Wednesday, September 23, 2020

C# .NET How to get Animation, Alpha, EXIF, ICC flags from header of WebP image file - partially loaded file

 The order to consume the header flags was clearly laid out in the Webp RIFF container specification, but getting the bits was tricky. Since even taking little-endian into account and reversing the bit values in the byte, which then aligning the indices to the spec, it still failed. Turns out it was the bit shifting math is using little-endian mode. Therefore, the flags are read in reverse order. 

Blade Runner.webp animated



Source Code


using System;
using System.Net; 
using System.Collections.Generic;
using System.IO;
using System.Drawing;
using System.Linq;
using System.Diagnostics; 

public class Program
{
	//https://stackoverflow.com/questions/111345/getting-image-dimensions-without-reading-the-entire-file/60667939#60667939
    //DecodeWebP reads only lossless :( 
	
	//My version improves DecodeWebP to read all webp formats, lossy, lossless and extended! 
    //https://metadataconsulting.blogspot.com/2020/09/CSharp-dotNET-How-to-get-Animation-Alpha-EXIF-ICC-flags-from-header-of-WebP-image-file-partially-loaded-file.html
	              
	internal static class ImageHelper
    {
        const string errorMessage = "Could not recognise image format.";

        private static Dictionary<byte[], Func<BinaryReader, Size>> imageFormatDecoders = new Dictionary<byte[], Func<BinaryReader, Size>>()
        {
            { new byte[] { 0x42, 0x4D }, DecodeBitmap },
            { new byte[] { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 }, DecodeGif },
            { new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 }, DecodeGif },
            { new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }, DecodePng },
            { new byte[] { 0xff, 0xd8 }, DecodeJfif },
            { new byte[] { 0x52, 0x49, 0x46, 0x46 }, DecodeWebP },
        };

        /// <summary>        
        /// Gets the dimensions of an image.        
        /// </summary>        
        /// <param name="path">The path of the image to get the dimensions of.</param>        
        /// <returns>The dimensions of the specified image.</returns>        
        /// <exception cref="ArgumentException">The image was of an unrecognised format.</exception>            
        public static Size GetDimensions(BinaryReader binaryReader)
        {
            int maxMagicBytesLength = imageFormatDecoders.Keys.OrderByDescending(x => x.Length).First().Length;
            byte[] magicBytes = new byte[maxMagicBytesLength];
            for (int i = 0; i < maxMagicBytesLength; i += 1)
            {
                magicBytes[i] = binaryReader.ReadByte();
                foreach (var kvPair in imageFormatDecoders)
                {
                    if (StartsWith(magicBytes, kvPair.Key))
                    {
                        Console.WriteLine(kvPair.Value.Method);
						return kvPair.Value(binaryReader);
						
                    }
                }
            }

            throw new ArgumentException(errorMessage, "binaryReader");
        }
        
        /// <summary>
        /// Gets the dimensions of an image.
        /// </summary>
        /// <param name="path">The path of the image to get the dimensions of.</param>
        /// <returns>The dimensions of the specified image.</returns>
        /// <exception cref="ArgumentException">The image was of an unrecognized format.</exception>
        public static Size GetDimensions(string path)
        {
            using (BinaryReader binaryReader = new BinaryReader(File.OpenRead(path)))
            {
                try
                {
                    return GetDimensions(binaryReader);
                }
                catch (ArgumentException e)
                {
                    if (e.Message.StartsWith(errorMessage))
                    {
                        throw new ArgumentException(errorMessage, "path", e);
                    }
                    else
                    {
                        throw e;
                    }
                }
            }
        }
		
		 /// <summary>
        /// Gets the dimensions of an image.
        /// </summary>
        /// <param name="path">The path of the image to get the dimensions of.</param>
        /// <returns>The dimensions of the specified image.</returns>
        /// <exception cref="ArgumentException">The image was of an unrecognized format.</exception>
        public static Size GetDimensions(MemoryStream ms)
        {
            using (BinaryReader binaryReader = new BinaryReader(ms))
            {
                try
                {
                    return GetDimensions(binaryReader);
                }
                catch (ArgumentException e)
                {
                    if (e.Message.StartsWith(errorMessage))
                    {
                        throw new ArgumentException(errorMessage, "path", e);
                    }
                    else
                    {
                        throw e;
                    }
                }
            }
        }


        private static bool StartsWith(byte[] thisBytes, byte[] thatBytes)
        {
            for (int i = 0; i < thatBytes.Length; i += 1)
            {
                if (thisBytes[i] != thatBytes[i])
                {
                    return false;
                }
            }

            return true;
        }

        private static short ReadLittleEndianInt16(BinaryReader binaryReader)
        {
            byte[] bytes = new byte[sizeof(short)];

            for (int i = 0; i < sizeof(short); i += 1)
            {
                bytes[sizeof(short) - 1 - i] = binaryReader.ReadByte();
            }
            return BitConverter.ToInt16(bytes, 0);
        }

        private static int ReadLittleEndianInt32(BinaryReader binaryReader)
        {
            byte[] bytes = new byte[sizeof(int)];
            for (int i = 0; i < sizeof(int); i += 1)
            {
                bytes[sizeof(int) - 1 - i] = binaryReader.ReadByte();
            }
            return BitConverter.ToInt32(bytes, 0);
        }

        private static Size DecodeBitmap(BinaryReader binaryReader)
        {
            binaryReader.ReadBytes(16);
            int width = binaryReader.ReadInt32();
            int height = binaryReader.ReadInt32();
            return new Size(width, height);
        }

        private static Size DecodeGif(BinaryReader binaryReader)
        {
            int width = binaryReader.ReadInt16();
            int height = binaryReader.ReadInt16();
            return new Size(width, height);
        }

        private static Size DecodePng(BinaryReader binaryReader)
        {
            binaryReader.ReadBytes(8);
            int width = ReadLittleEndianInt32(binaryReader);
            int height = ReadLittleEndianInt32(binaryReader);
            return new Size(width, height);
        }

        private static Size DecodeJfif(BinaryReader binaryReader)
        {
            while (binaryReader.ReadByte() == 0xff)
            {
                byte marker = binaryReader.ReadByte();
                short chunkLength = ReadLittleEndianInt16(binaryReader);
                if (marker == 0xc0 || marker == 0xc2) // c2: progressive
                {
                    binaryReader.ReadByte();
                    int height = ReadLittleEndianInt16(binaryReader);
                    int width = ReadLittleEndianInt16(binaryReader);
                    return new Size(width, height);
                }

                if (chunkLength < 0)
                {
                    ushort uchunkLength = (ushort)chunkLength;
                    binaryReader.ReadBytes(uchunkLength - 2);
                }
                else
                {
                    binaryReader.ReadBytes(chunkLength - 2);
                }
            }

            throw new ArgumentException(errorMessage);
        }
        
        //Other libs did not do it 
        //https://github.com/JosePineiro/WebP-wrapper/blob/master/WebPTest/WebPWrapper.cs
        //https://github.com/JimBobSquarePants/ImageProcessor/blob/6092da59e9aa4975e564002ef3c782a8f6bf3384/src/Plugins/ImageProcessor/ImageProcessor.Plugins.WebP/Imaging/Formats/WebPFormat.cs
      
		//fast
        private static Size DecodeWebP(BinaryReader binaryReader)
        {
            //https://developers.google.com/speed/webp/docs/riff_container
            //var riffseg = binaryReader.ReadBytes(4); //already offset 4 bytes 
            //var sizebytes = binaryReader.ReadBytes(4); // Size

            var size = binaryReader.ReadUInt32(); // Size - start at offset 4 
            
            var webp = binaryReader.ReadBytes(4); // start 8 offset

            var type = binaryReader.ReadBytes(4); // start 12 offset - VP8[ ] determination

            string VP8Type = System.Text.Encoding.UTF8.GetString(type);
			
			Console.WriteLine("VP8Type=\""+VP8Type+"\""); 

            int x = 0; 
            int y = 0;

            if (VP8Type == "VP8X") //Extra format - https://developers.google.com/speed/webp/docs/riff_container#extended_file_format
            { 
                
				binaryReader.ReadBytes(4); //skip 4 bytes
                
				byte ICCABit = binaryReader.ReadByte(); //read 1 byte for all header bit flags

                ////////////////////////////////////////////
                ///////// LITTLE INDIAN MATH! REVERSE ORDER - https://developers.google.com/speed/webp/docs/riff_container#extended_file_format
                ////////////////////////////////////////////
                
                int bitNumber = 0;
                //var bitR1C = (((ICCABit >> bitNumber) & 1) != 0); //Originally attempt at order 
                bool bitR3C = (((ICCABit >> bitNumber) & 1) != 0); 
                
                bitNumber = 1; 
                //var bitR2C = (((ICCABit >> bitNumber) & 1) != 0); //Originally attempt at order 
                var bitAniC = (((ICCABit >> bitNumber) & 1) != 0); 
				Console.WriteLine("has Animantion ? {0}", bitAniC); 

                bitNumber = 2; 
                //var bitICCC = (((ICCABit >> bitNumber) & 1) != 0); //Originally attempt at order 
                var bitXMPC = (((ICCABit >> bitNumber) & 1) != 0);
				Console.WriteLine("has XMP ? {0}", bitXMPC); 

                bitNumber = 3;
                //var bitAlphaC = (ICCABit >> bitNumber) & 1; //Originally attempt at order 
                bool bitExifC = (((ICCABit >> bitNumber) & 1) != 0); 
                Console.WriteLine("has EXIF ? {0}", bitExifC); 
				
                bitNumber = 4;
                //var bitExifC = (((ICCABit >> bitNumber) & 1) != 0); //Originally attempt at order 
                var bitAlphaC = (((ICCABit >> bitNumber) & 1) != 0);
				Console.WriteLine("has Alpha ? {0}", bitAlphaC); 
                
                bitNumber = 5;
                //var bitXMPC = (((ICCABit >> bitNumber) & 1) != 0);//Originally attempt at order 
                var bitICCC = (((ICCABit >> bitNumber) & 1) != 0); 
				Console.WriteLine("has ICC ? {0}", bitAniC); 
				
                bitNumber = 6;
                //var bitAniC = (((ICCABit >> bitNumber) & 1) != 0); //Originally attempt at order 
                var bitR2C = (((ICCABit >> bitNumber) & 1) != 0); 

                bitNumber = 7;
                //var bitR3C = (ICCABit >> bitNumber) & 1; //Originally attempt at order 
                var bitR1C = (((ICCABit >> bitNumber) & 1) != 0); 

				binaryReader.ReadBytes(3); 

                byte[] w = binaryReader.ReadBytes(3); //24bits for width

                x = 1 + (w[2] << 16 | w[1] << 8 | w[0]); //little endian

                byte[] h = binaryReader.ReadBytes(3); //24bits for height

                y = 1 + (h[2] << 16 | h[1] << 8 | h[0]); 

				return new Size(x, y);

            }
            else if (VP8Type == "VP8L") //Lossless - https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification#2_riff_header 
            {
                
                binaryReader.ReadBytes(4); //size
                byte[] sig = binaryReader.ReadBytes(1); //0x2f->47 1 byte signature
                if (sig[0] != 47) new Size(0, 0); 

                byte[] wh = binaryReader.ReadBytes(4); //width and height in 1 read
                x = 1 + (((wh[1] & 0x3F) << 8) | wh[0]); //{1 + ((($b1 & 0x3F) << 8) | $b0)} - https://blog.tcl.tk/38137  
                y = 1 + (((wh[3] & 0xF) << 10) | (wh[2] << 2) | ((wh[1] & 0xC0) >> 6)); //{1 + ((($b3 & 0xF) << 10) | ($b2 << 2) | (($b1 & 0xC0) >> 6))}]

				return new Size(x, y);

            }
            else if (VP8Type == "VP8 ") //Lossy - https://tools.ietf.org/html/rfc6386#section-9.1
            {

                //Lossy - https://tools.ietf.org/html/rfc6386#section-9.1 hard to decipher
                //pc->Width      = swap2(*(unsigned short*)(c+3))&0x3fff;  0x3fff -> 16383 decimal  swap2 - big or little indian depending on machine 
                //pc->Height     = swap2(*(unsigned short*)(c+5))&0x3fff;
              
				//https://blog.tcl.tk/38137 - much better
                  
                binaryReader.ReadBytes(7); //move to offset 23 or 0x17, 23-12+4=7 - open webp lossy file https://developers.google.com/speed/webp/gallery1

                byte[] frameTag = binaryReader.ReadBytes(3); //$b0 != 0x9d->157  || $b1 != 0x01>1 || $b2 != 0x2a->	42 
                if (frameTag[0] != 157 && frameTag[0] != 1 && frameTag[0] != 42) return new Size(0, 0); //invalid webp file

				//reads 2-bytes which is 16-bits, but we want only 14bits, so and it to 14 bits
                x = binaryReader.ReadUInt16() & 0x3fff;    //$width & 0x3fff -> & 0b00_11111111111111 c#7.0 above only
                y = binaryReader.ReadUInt16() & 0x3fff;    //$height & 0x3fff
            
				return new Size(x, y);
            }
            
            return new Size(0, 0);
            
        }

    }
	
	public static string GetFileNameFromURL(string hrefLink) //hack
	{
		string[] parts = hrefLink.Split('/');
		string fileName = string.Empty;

		if (parts.Length > 0)
			fileName = parts[parts.Length - 1];
		else
			fileName = hrefLink;

		return fileName;
	}

	public static void Main()
	{
	    Stopwatch sw = new Stopwatch(); 
		//string webpURL = "https://www.gstatic.com/webp/gallery/1.sm.webp";//Lossy
		//string webpURL = "https://www.gstatic.com/webp/gallery3/2_webp_ll.webp";//Lossless
		//string webpURL = "https://www.gstatic.com/webp/gallery3/1_webp_a.webp";//Extended with alpha channel
		//string webpURL = "https://mathiasbynens.be/demo/animated-webp-supported.webp"; //animated
		//string webpURL = "http://blog.mindworkshop.com/image/webp003.webp"; //animal
	    //string webpURL = "https://1.bp.blogspot.com/-3_acVqHutV4/X2TU2606WuI/AAAAAAAAMos/hlTUnNtnSbUpTevEI2_gjrNkw8tp4kHPACNcBGAsYHQ/w640-h287/BladeRunner.webp"; //Google blogger converted to gif! 
		
		string webpURL = "https://res.cloudinary.com/demo/image/upload/fl_awebp/cell_animation.webp";
		string webpfile = GetFileNameFromURL(webpURL); 
		
		Size webpSize = new Size(); 
		
	    WebClient wc = new WebClient();
		using (MemoryStream stream = new MemoryStream(wc.DownloadData(webpURL)))
		{
         sw.Start(); 
		 webpSize = ImageHelper.GetDimensions(stream); 
		 sw.Stop(); 
			
		}
		Console.WriteLine("File \"{0}\" has dimensions [{1}w X {2}h] in {3} ms.", webpfile, webpSize.Width, webpSize.Height, sw.ElapsedMilliseconds);
		
	}
}

Saturday, September 19, 2020

What is current version of Microsoft Edge - live-ish

 

Pulled from wikipedia at

 

 

for mobile switch to desktop view

Friday, September 18, 2020

C# .NET How to get image dimensions from header of webP for all formats; lossy, lossless, extended - partially load image

Get image dimensions from header of WebP  for all formats; lossy, lossless and extended and is fast. 

Code for reading the header of WebP formats is not commonly available in C# .NET, so I had to go to the WebP Container Specification to build out the code. The code reads the 1st 30 bytes of a webp file only. This is fast, but could be faster, stay tuned.


Geneva Drive
Geneva Drive.webp


Handles animated webp formats as specified by "VP8X" type.

Source Code

using System;
using System.Net; 
using System.Collections.Generic;
using System.IO;
using System.Drawing;
using System.Linq;
using System.Diagnostics; 

public class Program
{
	//Org 	https://www.codeproject.com/Articles/35978/Reading-Image-Headers-to-Get-Width-and-Height
	//Mod 	https://stackoverflow.com/questions/111345/getting-image-dimensions-without-reading-the-entire-file/60667939#60667939
    //		DecodeWebP reads only lossless :( 
	
	//My version improved DecodeWebP method to read all webp formats; lossy, lossless and extended. 
                
	internal static class ImageHelper
    {
        const string errorMessage = "Could not recognise image format.";

        private static Dictionary<byte[], Func<BinaryReader, Size>> imageFormatDecoders = new Dictionary<byte[], Func<BinaryReader, Size>>()
        {
            { new byte[] { 0x42, 0x4D }, DecodeBitmap },
            { new byte[] { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 }, DecodeGif },
            { new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 }, DecodeGif },
            { new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }, DecodePng },
            { new byte[] { 0xff, 0xd8 }, DecodeJfif },
            { new byte[] { 0x52, 0x49, 0x46, 0x46 }, DecodeWebP },
        };

        /// <summary>        
        /// Gets the dimensions of an image.        
        /// </summary>        
        /// <param name="path">The path of the image to get the dimensions of.</param>        
        /// <returns>The dimensions of the specified image.</returns>        
        /// <exception cref="ArgumentException">The image was of an unrecognised format.</exception>            
        public static Size GetDimensions(BinaryReader binaryReader)
        {
            int maxMagicBytesLength = imageFormatDecoders.Keys.OrderByDescending(x => x.Length).First().Length;
            byte[] magicBytes = new byte[maxMagicBytesLength];
            for (int i = 0; i < maxMagicBytesLength; i += 1)
            {
                magicBytes[i] = binaryReader.ReadByte();
                foreach (var kvPair in imageFormatDecoders)
                {
                    if (StartsWith(magicBytes, kvPair.Key))
                    {
                        return kvPair.Value(binaryReader);
                    }
                }
            }

            throw new ArgumentException(errorMessage, "binaryReader");
        }
        
        /// <summary>
        /// Gets the dimensions of an image.
        /// </summary>
        /// <param name="path">The path of the image to get the dimensions of.</param>
        /// <returns>The dimensions of the specified image.</returns>
        /// <exception cref="ArgumentException">The image was of an unrecognized format.</exception>
        public static Size GetDimensions(string path)
        {
            using (BinaryReader binaryReader = new BinaryReader(File.OpenRead(path)))
            {
                try
                {
                    return GetDimensions(binaryReader);
                }
                catch (ArgumentException e)
                {
                    if (e.Message.StartsWith(errorMessage))
                    {
                        throw new ArgumentException(errorMessage, "path", e);
                    }
                    else
                    {
                        throw e;
                    }
                }
            }
        }
		
		 /// <summary>
        /// Gets the dimensions of an image.
        /// </summary>
        /// <param name="path">The path of the image to get the dimensions of.</param>
        /// <returns>The dimensions of the specified image.</returns>
        /// <exception cref="ArgumentException">The image was of an unrecognized format.</exception>
        public static Size GetDimensions(MemoryStream ms)
        {
            using (BinaryReader binaryReader = new BinaryReader(ms))
            {
                try
                {
                    return GetDimensions(binaryReader);
                }
                catch (ArgumentException e)
                {
                    if (e.Message.StartsWith(errorMessage))
                    {
                        throw new ArgumentException(errorMessage, "path", e);
                    }
                    else
                    {
                        throw e;
                    }
                }
            }
        }


        private static bool StartsWith(byte[] thisBytes, byte[] thatBytes)
        {
            for (int i = 0; i < thatBytes.Length; i += 1)
            {
                if (thisBytes[i] != thatBytes[i])
                {
                    return false;
                }
            }

            return true;
        }

        private static short ReadLittleEndianInt16(BinaryReader binaryReader)
        {
            byte[] bytes = new byte[sizeof(short)];

            for (int i = 0; i < sizeof(short); i += 1)
            {
                bytes[sizeof(short) - 1 - i] = binaryReader.ReadByte();
            }
            return BitConverter.ToInt16(bytes, 0);
        }

        private static int ReadLittleEndianInt32(BinaryReader binaryReader)
        {
            byte[] bytes = new byte[sizeof(int)];
            for (int i = 0; i < sizeof(int); i += 1)
            {
                bytes[sizeof(int) - 1 - i] = binaryReader.ReadByte();
            }
            return BitConverter.ToInt32(bytes, 0);
        }

        private static Size DecodeBitmap(BinaryReader binaryReader)
        {
            binaryReader.ReadBytes(16);
            int width = binaryReader.ReadInt32();
            int height = binaryReader.ReadInt32();
            return new Size(width, height);
        }

        private static Size DecodeGif(BinaryReader binaryReader)
        {
            int width = binaryReader.ReadInt16();
            int height = binaryReader.ReadInt16();
            return new Size(width, height);
        }

        private static Size DecodePng(BinaryReader binaryReader)
        {
            binaryReader.ReadBytes(8);
            int width = ReadLittleEndianInt32(binaryReader);
            int height = ReadLittleEndianInt32(binaryReader);
            return new Size(width, height);
        }

        private static Size DecodeJfif(BinaryReader binaryReader)
        {
            while (binaryReader.ReadByte() == 0xff)
            {
                byte marker = binaryReader.ReadByte();
                short chunkLength = ReadLittleEndianInt16(binaryReader);
                if (marker == 0xc0 || marker == 0xc2) // c2: progressive
                {
                    binaryReader.ReadByte();
                    int height = ReadLittleEndianInt16(binaryReader);
                    int width = ReadLittleEndianInt16(binaryReader);
                    return new Size(width, height);
                }

                if (chunkLength < 0)
                {
                    ushort uchunkLength = (ushort)chunkLength;
                    binaryReader.ReadBytes(uchunkLength - 2);
                }
                else
                {
                    binaryReader.ReadBytes(chunkLength - 2);
                }
            }

            throw new ArgumentException(errorMessage);
        }
        
        //Other libs did not do it 
        //https://github.com/JosePineiro/WebP-wrapper/blob/master/WebPTest/WebPWrapper.cs
        //https://github.com/JimBobSquarePants/ImageProcessor/blob/6092da59e9aa4975e564002ef3c782a8f6bf3384/src/Plugins/ImageProcessor/ImageProcessor.Plugins.WebP/Imaging/Formats/WebPFormat.cs
      
		//fast
        private static Size DecodeWebP(BinaryReader binaryReader)
        {
            //https://developers.google.com/speed/webp/docs/riff_container
			//var riff = binaryReader.ReadBytes(4); //already offset 4 bytes to read R I F F 
            
			uint size = binaryReader.ReadUInt32() + 8; // Size - start at offset 4,  length+8! https://en.wikipedia.org/wiki/WebP - actual not on disk size
			
            var webp = binaryReader.ReadBytes(4); // start 8 offset - https://en.wikipedia.org/wiki/WebP
            if (webp[0] != 87 && webp[1] != 69 && webp[2] != 66 && webp[3] != 80) //must match W E B P
                return new Size(0, 0);

			//or use binaryReader.ReadBytes(8); to avoid skip above
			
            var type = binaryReader.ReadBytes(4); // start 12 offset - VP8[ ] determination

            string VP8Type = System.Text.Encoding.UTF8.GetString(type);
			
			Console.WriteLine("VP8Type=\""+VP8Type+"\" of size "+ size.ToString("#,##0")+" bytes."); 

            int x = 0; 
            int y = 0;

            if (VP8Type == "VP8X") //Extra format - https://developers.google.com/speed/webp/docs/riff_container#extended_file_format
            { 
                //skip 32 bits or 8 bytes for Alpha, Exif, XMP flags... in VP8X header
                binaryReader.ReadBytes(8);

                byte[] w = binaryReader.ReadBytes(3); //24bits for width

                x = 1 + (w[2] << 16 | w[1] << 8 | w[0]); //little endian

                byte[] h = binaryReader.ReadBytes(3); //24bits for height

                y = 1 + (h[2] << 16 | h[1] << 8 | h[0]); 

				return new Size(x, y);

            }
            else if (VP8Type == "VP8L") //Lossless - https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification#2_riff_header 
            {
                
                binaryReader.ReadBytes(4); //size
                byte[] sig = binaryReader.ReadBytes(1); //0x2f->47 1 byte signature
                if (sig[0] != 47) return new Size(0, 0); 

                byte[] wh = binaryReader.ReadBytes(4); //width and height in 1 read
                x = 1 + (((wh[1] & 0x3F) << 8) | wh[0]); //{1 + ((($b1 & 0x3F) << 8) | $b0)} - https://blog.tcl.tk/38137  
                y = 1 + (((wh[3] & 0xF) << 10) | (wh[2] << 2) | ((wh[1] & 0xC0) >> 6)); //{1 + ((($b3 & 0xF) << 10) | ($b2 << 2) | (($b1 & 0xC0) >> 6))}]

				return new Size(x, y);

            }
            else if (VP8Type == "VP8 ") //Lossy - https://tools.ietf.org/html/rfc6386#section-9.1
            {

                //Lossy - https://tools.ietf.org/html/rfc6386#section-9.1  - hard to decipher this 
                //pc->Width      = swap2(*(unsigned short*)(c+3))&0x3fff;  0x3fff -> 16383 decimal  swap2 - big or little indian depending on machine 
                //pc->Height     = swap2(*(unsigned short*)(c+5))&0x3fff;
              
				//https://blog.tcl.tk/38137 - much better
                  
                binaryReader.ReadBytes(7); //move to offset 23 or 0x17, 23-12+4=7 - open webp lossy file https://developers.google.com/speed/webp/gallery1

                byte[] frameTag = binaryReader.ReadBytes(3); 
				
				//    $b0 != 0x9d->157  || $b1 != 0x01>1 || $b2 != 0x2a->	42 
                if (frameTag[0] != 157 || frameTag[1] != 1 || frameTag[2] != 42) 
					return new Size(0, 0); //invalid webp file

				//reads 2 bytes which is 16-bits, but we want only 14 bits, shrink
                x = binaryReader.ReadUInt16() & 0x3fff;    //$width & 0x3fff -> & 0b00_11111111111111 c#7.0 above only
                y = binaryReader.ReadUInt16() & 0x3fff;    //$height & 0x3fff
            
				return new Size(x, y);
            }
            
            return new Size(0, 0);
            			
        }

    }
	
	public static string GetFileNameFromURL(string hrefLink) //hack
	{
		string[] parts = hrefLink.Split('/');
		string fileName = string.Empty;

		if (parts.Length > 0)
			fileName = parts[parts.Length - 1];
		else
			fileName = hrefLink;

		return fileName;
	}

	public static void Main()
	{
	    Stopwatch sw = new Stopwatch(); 
		//string webpURL = "https://www.gstatic.com/webp/gallery/1.sm.webp";//Lossy
		//string webpURL = "https://www.gstatic.com/webp/gallery3/2_webp_ll.webp";//Lossless
		//string webpURL = "https://www.gstatic.com/webp/gallery3/1_webp_a.webp";//Extended with alpha channel
		//string webpURL = "https://mathiasbynens.be/demo/animated-webp-supported.webp"; //animated
		//string webpURL = "http://blog.mindworkshop.com/image/webp003.webp"; //animal
		string webpURL = "https://file-examples-com.github.io/uploads/2020/03/file_example_WEBP_1500kB.webp"; //1.5 Mb
		
	    string webpfile = GetFileNameFromURL(webpURL); 
		
		Size webpSize = new Size(); 
		
	    WebClient wc = new WebClient();
		using (MemoryStream stream = new MemoryStream(wc.DownloadData(webpURL))) //this would normally be a path, but can't do that here
		{
         sw.Start(); 
		 webpSize = ImageHelper.GetDimensions(stream); 
	     sw.Stop(); 
			
		}
		Console.WriteLine("WebP file \"{0}\" has dimensions [{1}w X {2}h] in {3}ms.", webpfile, webpSize.Width, webpSize.Height, sw.ElapsedMilliseconds);
	}
}