Sunday, June 7, 2020

CSharp - Getting the proper number of bits used in a image or bits per pixel or bits per channel

Gets the colloquial "Bit Depth" of an image, but technically gets the bits per channel.
This sample solution illustrates the shortcomings of standard windows image library and uses a third party SharpDX lib to get image pixel format, and thus the "bit depth" of an image. It seems to be the only library that works consistently, in regards to getting the correct info for an image. 

System.Windows.Media and System.Drawing report different pixel formats for same image and incorrectly for 16-bit images in particular.

This is
custom solution is provided for bits per pixel for an image which is really report bits per channel, but commonly misinterpreted. See below.

Test Suite

http://www.schaik.com/pngsuite/pngsuite_bas_png.html













Motivation

There is a great deal of confusion, for the general public regarding the expectation what bit depth is, it loosely refers to number of bits used in an image versus number of bits used per each channel or color. When referring to a pixel, the concept can be defined as bits per pixel (bpp), which specifies the number of bits used. When referring to a color component, the concept can be defined as bits per component, bits per channel, bits per sample, bits per color (all three abbreviated bpc), and also bits per pixel component, bits per color channel or bits per sample (bps).
Refer to https://en.wikipedia.org/wiki/Color_depth for full digest.

 Download GitHub repo which includes full Visual Studio 2010 project using .NET 4.0 (for max upgrade-ability) and references to latest SharpDX lib 4.2.0

Note: This took many hours of investigation trying a number of Windows Imaging Component (WIC) libraries and image metadata tools, such as ImageMagick,  ExifTool (worked but slow), Exiv2 and slew of image metadata projects for images and lasting the ever promising Microsoft Windows API Code Pack. All failed or did not provide this information consistently for every image.

        /// Gets bits per channel strictly speaking, but is this commonly confused as bits per pixel and we'll use this expectation
        /// </summary>
        /// <param name="pixelFormat">System.Windows.Media.PixelFormat</param>
        /// <returns>int bits per channel</returns>
        /// <author> MetadataConsulting.ca</author>
        /// <created>Sun 07-Jun-20 1:27am</created>
        //  https://en.wikipedia.org/wiki/Color_depth
        //  get from source - https://referencesource.microsoft.com/#PresentationCore/Core/CSharp/System/Windows/Media/PixelFormat.cs
        //License - https://www.gnu.org/licenses/gpl-3.0.html        
        public int GetBitsPerPixelakaChannel(System.Windows.Media.PixelFormat pixelFormat)
        {
            
            //var x = (System.Windows.Media.PixelFormat)pixelformat;  //no conversion available

            string strPF = pixelFormat.ToString(); 

            switch (strPF)
            {
                case "Default": 
                    return -1; //Gets the pixel format that is best suited for the particular operation or here not found!

                case "Indexed1":
                    return 1; //Gets the pixel format specifying a paletted bitmap with 2 colors.

                case "Indexed2":
                    return 2; //Gets the pixel format specifying a paletted bitmap with 4 colors.

                case "Indexed4":
                    return 4; //Gets the pixel format specifying a paletted bitmap with 16 colors.

                case "Indexed8":
                    return 8; //Gets the pixel format specifying a paletted bitmap with 256 colors.

                case "BlackWhite":
                    return 1; //Gets the black and white pixel format which displays one bit of data per pixel as either black or white.

                case "Gray2":
                    return 2; //Gets the Gray2 pixel format which displays a 2 bits-per-pixel grayscale channel, allowing 4 shades of gray.

                case "Gray4":
                    return 4; //Gets the Gray4 pixel format which displays a 4 bits-per-pixel grayscale channel, allowing 16 shades of gray.

                case "Gray8":
                    return 8; //Gets the Gray8 pixel format which displays an 8 bits-per-pixel grayscale channel, allowing 256 shades of gray.

                case "Gray16":
                    return 16; //Gets the Gray16 pixel format which displays a 16 bits-per-pixel grayscale channel, allowing 65536 shades of gray. This format has a gamma of 1.0.

                case "Gray32Float":
                    return 32; //Gets the Gray32Float pixel format. Gray32Float displays a 32 bits per pixel (BPP) grayscale channel, allowing over 4 billion shades of gray. This format has a gamma of 1.0.

                case "Bgr555":
                    return 5; //Gets the Bgr555 pixel format. Bgr555 is a sRGB format with 16 bits per pixel (BPP). Each color channel (blue, green, and red) is allocated 5 bits per pixel (BPP).

                case "Bgr565"://Note: Hard to choose a good value here, unless we return a decimal perhaps 5.65 then
                    return 6; //Gets the Bgr565 pixel format. Bgr565 is a sRGB format with 16 bits per pixel(BPP).Each color channel(blue, green, and red) is allocated 5, 6, and 5 bits per pixel(BPP) respectively.

                case "Bgr101010":
                    return 10; //Gets the Bgr101010 pixel format. Bgr101010 is a sRGB format with 32 bits per pixel (BPP). Each color channel (blue, green, and red) is allocated 10 bits per pixel (BPP).

                case "Bgr24":
                    return 8; //Gets the Bgr24 pixel format. Bgr24 is a sRGB format with 24 bits per pixel (BPP). Each color channel (blue, green, and red) is allocated 8 bits per pixel (BPP).

                case "Rgb24":
                    return 8; //Gets the Rgb24 pixel format. Rgb24 is a sRGB format with 24 bits per pixel (BPP). Each color channel (red, green, and blue) is allocated 8 bits per pixel (BPP).

                case "Bgr32":
                    return 8; //Gets the Bgr32 pixel format. Bgr32 is a sRGB format with 32 bits per pixel (BPP). Each color channel (blue, green, and red) is allocated 8 bits per pixel (BPP).

                case "Bgra32":
                    return 8; //Gets the Bgra32 pixel format. Bgra32 is a sRGB format with 32 bits per pixel (BPP). Each channel (blue, green, red, and alpha) is allocated 8 bits per pixel (BPP).

                case "Pbgra32":
                    return 8; //Gets the Pbgra32 pixel format. Pbgra32 is a sRGB format with 32 bits per pixel (BPP). Each channel (blue, green, red, and alpha) is allocated 8 bits per pixel (BPP). Each color channel is pre-multiplied by the alpha value.

                case "Rgb48":
                    return 16; //Gets the Rgb48 pixel format. Rgb48 is a sRGB format with 48 bits per pixel (BPP). Each color channel (red, green, and blue) is allocated 16 bits per pixel (BPP). This format has a gamma of 1.0.

                case "Rgba64":
                    return 16; //Gets the Rgba64 pixel format. Rgba64 is an sRGB format with 64 bits per pixel (BPP). Each channel (red, green, blue, and alpha) is allocated 16 bits per pixel (BPP). This format has a gamma of 1.0.

                case "Prgba64":
                    return 32; //Gets the Prgba64 pixel format. Prgba64 is a sRGB format with 64 bits per pixel (BPP). Each channel (blue, green, red, and alpha) is allocated 32 bits per pixel (BPP). Each color channel is pre-multiplied by the alpha value. This format has a gamma of 1.0.

        
                case "Rgb128Float":
                    return 32; //Gets the Rgb128Float pixel format. Rgb128Float is a ScRGB format with 128 bits per pixel (BPP). Each color channel is allocated 32 BPP. This format has a gamma of 1.0.

                case "Rgba128Float":
                    return 32; //Gets the Rgba128Float pixel format. Rgba128Float is a ScRGB format with 128 bits per pixel (BPP). Each color channel is allocated 32 bits per pixel (BPP). This format has a gamma of 1.0.

                case "Prgba128Float":
                    return 32; //Gets the Prgba128Float pixel format. Prgba128Float is a ScRGB format with 128 bits per pixel (BPP). Each channel (red, green, blue, and alpha) is allocated 32 bits per pixel (BPP). Each color channel is pre-multiplied by the alpha value. This format has a gamma of 1.0.

                case "Cmyk32":
                    return 8; //Gets the Cmyk32 pixel format which displays 32 bits per pixel (BPP) with each color channel (cyan, magenta, yellow, and black) allocated 8 bits per pixel (BPP).
            }

            return -1; 
        }

Thursday, June 4, 2020

CSharp - Fastest way to find a number/integer in a string



After an extensive review of getting a lead integer from a string, see my post here.


Here's the fastest way in C# to get the 1st integer in a string.

This is not Unicode compliant, but works for English speaking populations using ASCII numerals 0-9.


Code

using System;
using System.Diagnostics;
using System.Text; 
using System.Text.RegularExpressions; 

public static class StringExtensions {
 
        /// <summary>
        /// Fastest way to to find 1st number in a string and convert it into an integer
        /// </summary>
        /// <param name="intStr"></param>
        /// <returns></returns>
        //  https://metadataconsulting.blogspot.com/2020/06/CSharp-Fastest-way-to-find-a-number-or-integer-in-a-string.html
        public static int GetFirstIntFast(this string intStr)
        {
 
            int sum = 0; //must be zero
            char[] n = intStr.ToCharArray(); //fastest way to index a string
            int idxFirstDigit = -1;  

            for (int i = 0; i < n.Length; i++)
            {
                if (n[i] >= 48 && n[i] <= 57)  //'0'=48 and '9'=57 get lead number only
                {
                    if (idxFirstDigit == -1) idxFirstDigit = i; 
                    int z = sum * 10 + (n[i] - 48);
                    if (z < 0) //we get into negative, if over int.MaxValue
                        return int.MaxValue; //or throw error
                    sum = z;
                }
                else if (idxFirstDigit>-1)
                    break;
                    //return int.MinValue; //or throw error
            }

            if (intStr.IndexOf('-') == idxFirstDigit-1) //chek for neg sign
               sum *= -1;
            
            return sum;

        }

}

public class Program
{
 
 public static void Main()
 {
  
            Stopwatch sw = new Stopwatch();
            sw.Start();
            Console.WriteLine("aaaa12451a 1".GetFirstIntFast());
            sw.Stop();
            Console.WriteLine(sw.ElapsedTicks.ToString() + " ticks");
            sw.Reset();
            sw.Start();
            Console.WriteLine("aaaaa-12452a 2".GetFirstIntFast());
            sw.Stop();
            Console.WriteLine(sw.ElapsedTicks.ToString() + " ticks");
            sw.Reset();
            sw.Start();
            Console.WriteLine("aaaaaa+12453a 3".GetFirstIntFast());
            sw.Stop();
            Console.WriteLine(sw.ElapsedTicks.ToString() + " ticks");
            sw.Reset();
            sw.Start();
            Console.WriteLine("aaaaaa".GetFirstIntFast());
            sw.Stop();
            Console.WriteLine(sw.ElapsedTicks.ToString() + " ticks");
            Console.WriteLine("1122222222222222222222".GetFirstIntFast());
            sw.Stop();
            Console.WriteLine(sw.ElapsedTicks.ToString() + " ticks");
 
 }
}

Monday, June 1, 2020

CSharp - Get a number/integer from a string fast, a speed comparison

Here's a few implementations to extract a integer found at beginning of a string.
See my post on the fastest method to extract the 1st found integer anywhere in a string.

The comparisons below are predicated on first extracting the lead d
igits found in a string ( sign integer allowed), and then converting the string of numbers into an integer.

This is not Unicode compliant, but works for English speaking populations using ASCII numerals 0-9.





Code in case above service perishes. 

using System;
using System.Diagnostics;
using System.Text; 
using System.Text.RegularExpressions; 


public static class StringExtensions {
 
       
        public static string GetAllIntRegex(this string numint)
        {
          return Regex.Replace(numint, "[^0-9]", "");
    
        }
        

        public static string GetLeadIntFast(this string numint)
        {
            bool minus = false; //we pass in absolute number
            if (numint.IndexOf('-') == 0)
            {
                minus = true;
                numint = numint.Replace("-", ""); //we expect this to be in first place, not bullet proof but GE
            }
            else if (numint.IndexOf('+') == 0)
                numint = numint.Replace("+", "");

            
            char[] n = numint.ToCharArray();
            StringBuilder sb = new StringBuilder(); 
            for (int i = 0; i < n.Length; i++)
            {
                if (!char.IsDigit(n[i]))
                    break; 

                sb.Append(n[i]); 
            }

            if (minus)
                return string.Concat("-", sb.ToString());
            
            return sb.ToString();
        }

        public static string GetLeadIntFaster(this string numint)
        {
            bool minus = false; //we pass in absolute number
            if (numint.IndexOf('-') == 0)
            {
                minus = true;
                numint = numint.Replace("-", ""); //we expect this to be in first place
            }
            else if (numint.IndexOf('+') == 0)
                numint = numint.Replace("+", "");


            char[] n = numint.ToCharArray();
            int exit = 0;
            for (int i = 0; i < n.Length; i++)
            {
                if (!char.IsDigit(n[i])) {
                    exit = i; 
                    break;
                }
            }
            Array.Resize(ref n, exit);

            if (minus)
                return string.Concat("-", new string(n));
            
            return new string(n); 
        }
        
        public static string GetLeadIntFastest(this string numint)
        {
            bool minus = false; //we pass in absolute number
            if (numint.IndexOf('-') == 0)
            {
                minus = true;
                numint = numint.Substring(1); //we expect this to be in first place
            }
            else if (numint.IndexOf('+') == 0)
                numint = numint.Substring(1);


            char[] n = numint.ToCharArray(); //faster indexing that string indexing
            int exit = 0;
            for (int i = 0; i < n.Length; i++)
            {
                if (!char.IsDigit(n[i]))
                {
                    exit = i;
                    break;
                }
            }
            Array.Resize(ref n, exit);

            if (minus)
                return string.Concat("-",  new string(n));

            return new string(n); 
        }

        public static int TryParser(this string numint)
        {
            int y = 0;
            //This is a fair comparison, we must get digits only
            Int32.TryParse(numint.GetLeadIntFast(), out y); //this alone is fast at 502 ticks
            return y;

        }

        public static int TryParserFaster(this string numint)
        {
            int y = 0;
            //This is a fair comparison, we must get digits only
            Int32.TryParse(numint.GetLeadIntFaster(), out y); //this alone is fast at 502 ticks
            return y;

        }

        public static int TryParserFastest(this string numint)
        {
            int y = 0;
            //This is a fair comparison, we must get digits only
            Int32.TryParse(numint.GetLeadIntFastest(), out y); //this alone is fast at 502 ticks
            return y;

        }

        public static int TryParserRegex(this string numint)
        {
            int y = 0;
            //This is a fair comparison, we must get digits only
            Int32.TryParse(numint.GetAllIntRegex(), out y); //this alone is fast at 502 ticks
            return y;


        }
        /// <summary>
        /// Fastest wat to convert a numeric string into an int
        /// </summary>
        /// <param name="numint">must be numbers and signs only</param>
        /// <returns></returns>
        public static int StartsWithInt(this string numint)
        {
            bool minus = false; //we pass in absolute number
            if (numint.IndexOf('-') == 0)
            {
                minus = true;
                numint = numint.Replace("-", ""); //we expect this to be in first place, not bullet proof but GE
            }
            else if (numint.IndexOf('+') == 0)
                numint = numint.Replace("+", "");

            int total = 0; //must be zero
            for (int i = 0; i < numint.Length; i++)
            {
                if (numint[i] >= '0' && numint[i] <= '9')  //get lead number only
                {
                    if (total * 10 + (numint[i] - '0') < 0) //we get into negative, if over int.MaxValue
                        return int.MaxValue; //return what you want or throw error

                    total = total * 10 + (numint[i] - '0');
                }
                else
                    break;
                //return int.MinValue;

            }

            if (minus) total *= -1;
            return total;

        }

        /// <summary>
        /// Fastest way to convert lead digits in a string into an int
        /// </summary>
        /// <param name="intStr">must be numbers and signs only</param>
        /// <returns></returns>
        public static int StartsWithIntFaster(this string intStr)
        {
 
            bool minus = false; //we pass in absolute number
            if (intStr.IndexOf('-') == 0)
            {
                minus = true;
                intStr = intStr.Replace("-", ""); //we expect this to be in first place, not bullet proof but GE
            }
            else if (intStr.IndexOf('+') == 0)
                intStr = intStr.Replace("+", "");

            int sum = 0; //must be zero
            char[] n = intStr.ToCharArray();
       
            for (int i = 0; i < n.Length; i++)
            {
                if (n[i] >= 48 && n[i] <= 57)  //'0'=48 and '9'=57 get lead number only
                {
                    int z = sum * 10 + (n[i] - 48);  
                    if (z < 0) //we get into negative, if over int.MaxValue
                        return int.MaxValue; //return what you want or throw error
                    sum = z;
                }
                else
                    break; //returns lead digits
                           //or return int.MinValue;

            }

            if (minus) sum *= -1;
            return sum;

        }
        
        //WINNER  
        /// <summary>
        /// Fastest way to convert lead digits in a string into an int
        /// </summary>
        /// <param name="intStr">must be numbers and signs only</param>
        /// <returns></returns>
        public static int StartsWithIntFastest(this string intStr)
        {
 
            bool minus = false; //we need to pass in absolute number
            if (intStr.IndexOf('-') == 0)
            {
                minus = true;
                intStr = intStr.Substring(1); //we expect this to be in first place, not bullet proof but GE
            }
            else if (intStr.IndexOf('+') == 0)
                intStr = intStr.Substring(1);

            int sum = 0; //must be zer0
            char[] n = intStr.ToCharArray();

            for (int i = 0; i < n.Length; i++)
            {
                if (n[i] >= 48 && n[i] <= 57)  //'0'=48 and '9'=57 get lead number only
                {
                    int z = sum * 10 + (n[i] - 48);
                    if (z < 0) //we get into negative, if over int.MaxValue
                        return int.MaxValue; //return what you want or throw error
                    sum = z;
                }
                else
                    break; //returns lead digits
                           //return int.MinValue; //or throw error

            }

            if (minus) sum *= -1;
            return sum;

        }

}

public class Program
{
 
 public static void Main()
 {
  
            Stopwatch sw = new Stopwatch();
            sw.Start();
            Console.WriteLine("12451a 1".StartsWithInt());
            sw.Stop();
            Console.WriteLine(sw.ElapsedTicks.ToString() + " ticks");
            sw.Reset();
            sw.Start();
            Console.WriteLine("12452a 2".StartsWithIntFaster());
            sw.Stop();
            Console.WriteLine(sw.ElapsedTicks.ToString() + " ticks");
            sw.Reset();
            sw.Start();
            Console.WriteLine("Winner");
            Console.WriteLine("12453a 3".StartsWithIntFastest());
            sw.Stop();
            Console.WriteLine(sw.ElapsedTicks.ToString() + " ticks");
            sw.Reset();
            sw.Start();
            Console.WriteLine("12454a 4".TryParser());
            sw.Stop();
            Console.WriteLine(sw.ElapsedTicks.ToString() + " ticks");
            sw.Reset();
            sw.Start();
            Console.WriteLine("12455a 5".TryParserFaster());
            sw.Stop();
            Console.WriteLine(sw.ElapsedTicks.ToString() + " ticks");
            sw.Reset();
            sw.Start();
            Console.WriteLine("12456a 6".TryParserFastest());
            sw.Stop();
            Console.WriteLine(sw.ElapsedTicks.ToString() + " ticks");
            sw.Reset();
            sw.Start();
            Console.WriteLine("This grabs all numbers and NOT JUST LEAD digits!");
            Console.WriteLine("12457a 7".TryParserRegex());
            sw.Stop();
            Console.WriteLine(sw.ElapsedTicks.ToString() + " ticks");
  
  Console.WriteLine("0".StartsWithIntFastest());
  Console.WriteLine("10".StartsWithIntFastest());
  Console.WriteLine("-12345".StartsWithIntFastest());
  Console.WriteLine("1234".StartsWithIntFastest());
  Console.WriteLine("123".StartsWithIntFastest());
  Console.WriteLine("+12346".StartsWithIntFastest());
  Console.WriteLine("-1234".StartsWithIntFastest());
  Console.WriteLine("21474813649".StartsWithIntFastest()); 
 }
}