Advertisement
Guest User

BarcodeImaging

a guest
May 21st, 2013
228
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 47.06 KB | None | 0 0
  1. using System;
  2. using System.Drawing;
  3. using System.Drawing.Imaging;
  4. using System.Text;
  5. using System.Collections.Specialized;
  6. using System.Collections;
  7.  
  8. /// <summary>
  9. /// Barcode imaging class by Berend Engelbrecht (b.engelbrecht@gmail.com).
  10. /// See http://www.codeproject.com/KB/graphics/BarcodeImaging3.aspx
  11. ///
  12. /// Parts of this class are derived from an earlier code project by James Fitch (qlipoth).
  13. /// Used and published with permission of the original author.
  14. ///
  15. /// Licensed under The Code Project Open License (CPOL).
  16. /// See http://www.codeproject.com/info/cpol10.aspx
  17. /// </summary>
  18. public class BarcodeImaging
  19. {
  20.   #region Public types (used in public function parameters)
  21.   /// <summary>
  22.   /// Used to specify what barcode type(s) to detect.
  23.   /// </summary>
  24.   public enum BarcodeType
  25.   {
  26.     /// <summary>Not specified</summary>
  27.     None = 0,
  28.     /// <summary>Code39</summary>
  29.     Code39 = 1,
  30.     /// <summary>EAN/UPC</summary>
  31.     EAN = 2,
  32.     /// <summary>Code128</summary>
  33.     Code128 = 4,
  34.     /// <summary>Use BarcodeType.All for all supported types</summary>
  35.     All = Code39 | EAN | Code128
  36.  
  37.     // Note: Extend this enum with new types numbered as 8, 16, 32 ... ,
  38.     //       so that we can use bitwise logic: All = Code39 | EAN | <your favorite type here> | ...
  39.   }
  40.  
  41.   /// <summary>
  42.   /// Used to specify whether to scan a page in vertical direction,
  43.   /// horizontally, or both.
  44.   /// </summary>
  45.   public enum ScanDirection
  46.   {
  47.     /// <summary>Scan top-to-bottom</summary>
  48.     Vertical = 1,
  49.     /// <summary>Scan left-to-right</summary>
  50.     Horizontal = 2
  51.   }
  52.   #endregion
  53.  
  54.   #region Private constants and types
  55.   private struct BarcodeZone
  56.   {
  57.     public int Start;
  58.     public int End;
  59.   }
  60.  
  61.   /// <summary>
  62.   /// Structure used to return the processed data from an image
  63.   /// </summary>
  64.   private class histogramResult
  65.   {
  66.     /// <summary>Averaged image brightness values over one scanned band</summary>
  67.     public byte[] histogram;
  68.     /// <summary>Minimum brightness (darkest)</summary>
  69.     public byte min; //
  70.     /// <summary>Maximum brightness (lightest)</summary>
  71.     public byte max;
  72.  
  73.     public byte threshold;     // threshold brightness to detect change from "light" to "dark" color
  74.     public float lightnarrowbarwidth; // narrow bar width for light bars
  75.     public float darknarrowbarwidth;  // narrow bar width for dark bars
  76.     public float lightwiderbarwidth;  // width of most common wider bar for light bars
  77.     public float darkwiderbarwidth;   // width of most common wider bar for dark bars
  78.  
  79.     public BarcodeZone[] zones; // list of zones on the current band that might contain barcode data
  80.   }
  81.  
  82.   // General
  83.   private const int GAPFACTOR = 48;        // width of quiet zone compared to narrow bar
  84.   private const int MINNARROWBARCOUNT = 4; // minimum occurence of a narrow bar width
  85.  
  86.   // Code39
  87.   private const string STARPATTERN = "nwnnwnwnn"; // the pattern representing a star in Code39
  88.   private const float WIDEFACTOR = 2.0f; // minimum width of wide bar compared to narrow bar
  89.   private const int MINPATTERNLENGTH = 10; // length of one barcode digit + gap
  90.  
  91.   // Code128
  92.   private const int CODE128START = 103;    // Startcodes have index >= 103
  93.   private const int CODE128STOP = 106; // Stopcode has index 106
  94.   private const int CODE128C = 99;  // Switch to code page C
  95.   private const int CODE128B = 100; // Switch to code page B
  96.   private const int CODE128A = 101; // Switch to code page A
  97.  
  98.  
  99.   #endregion
  100.  
  101.   #region Private member variables
  102.   private static BarcodeType m_FullScanBarcodeTypes = BarcodeType.All;
  103.   private static bool m_bUseBarcodeZones = true;
  104.   #endregion
  105.  
  106.   #region Public properties used in configuration
  107.  
  108.   /// <summary>
  109.   /// Barcode types to be detected in FullScanPage. Set this to a specific
  110.   /// subset of types if you do not need "All" to be detected by default.
  111.   /// </summary>
  112.   public static BarcodeType FullScanBarcodeTypes
  113.   {
  114.     get { return m_FullScanBarcodeTypes; }
  115.     set { m_FullScanBarcodeTypes = value; }
  116.   }
  117.  
  118.   /// <summary>
  119.   /// Set UseBarcodeZones to false if you do not need this feature.
  120.   /// Barcode regions improve detection of multiple barcodes on one scan line,
  121.   /// but have a significant performance impact.
  122.   /// </summary>
  123.   public static bool UseBarcodeZones
  124.   {
  125.     get { return m_bUseBarcodeZones; }
  126.     set { m_bUseBarcodeZones = value; }
  127.   }
  128.   #endregion
  129.  
  130.   #region Public methods
  131.   /// <summary>
  132.   /// FullScanPage does a full scan of the active frame in the passed bitmap. This function
  133.   /// will scan both vertically and horizontally.
  134.   /// </summary>
  135.   /// <remarks>
  136.   /// By default FullScanPage will attempt to detect barcodes of all supported types. Assign
  137.   /// a subset to FullScanBarcodeTypes if your application does not need this.
  138.   ///
  139.   /// Use ScanPage instead of FullScanPage if you want to scan in one direction only,
  140.   /// or only for specific barcode types.
  141.   ///
  142.   /// For a multi-page tiff only one page is scanned. By default, the first page is used, but
  143.   /// you can scan other pages by calling bmp.SelectActiveFrame(FrameDimension.Page, pagenumber)
  144.   /// before calling FullScanPage.
  145.   /// </remarks>
  146.   /// <param name="CodesRead">Will contain detected barcode strings when the function returns</param>
  147.   /// <param name="bmp">Input bitmap</param>
  148.   /// <param name="numscans">Number of passes that must be made over the page.
  149.   /// 50 - 100 usually gives a good result.</param>
  150.   public static void FullScanPage(ref System.Collections.ArrayList CodesRead, Bitmap bmp, int numscans)
  151.   {
  152.     ScanPage(ref CodesRead, bmp, numscans, ScanDirection.Vertical, FullScanBarcodeTypes);
  153.     ScanPage(ref CodesRead, bmp, numscans, ScanDirection.Horizontal, FullScanBarcodeTypes);
  154.   }
  155.  
  156.   /// <summary>
  157.   /// Scans the active frame in the passed bitmap for barcodes.
  158.   /// </summary>
  159.   /// <param name="CodesRead">Will contain detected barcode strings when the function returns</param>
  160.   /// <param name="bmp">Input bitmap</param>
  161.   /// <param name="numscans">Number of passes that must be made over the page.
  162.   /// 50 - 100 usually gives a good result.</param>
  163.   /// <param name="direction">Scan direction</param>
  164.   /// <param name="types">Barcode types. Pass BarcodeType.All, or you can specify a list of types,
  165.   /// e.g., BarcodeType.Code39 | BarcodeType.EAN</param>
  166.   public static void ScanPage(ref System.Collections.ArrayList CodesRead, Bitmap bmp, int numscans, ScanDirection direction, BarcodeType types)
  167.   {
  168.     int iHeight, iWidth;
  169.     if (direction == ScanDirection.Horizontal)
  170.     {
  171.       iHeight = bmp.Width;
  172.       iWidth = bmp.Height;
  173.     }
  174.     else
  175.     {
  176.       iHeight = bmp.Height;
  177.       iWidth = bmp.Width;
  178.     }
  179.     if (numscans > iHeight) numscans = iHeight; // fix for doing full scan on small images
  180.     for (int i = 0; i < numscans; i++)
  181.     {
  182.       int y1 = (i * iHeight) / numscans;
  183.       int y2 = ((i + 1) * iHeight) / numscans;
  184.       string sCodesRead = ReadBarcodes(bmp, y1, y2, direction, types);
  185.  
  186.       if ((sCodesRead != null) && (sCodesRead.Length > 0))
  187.       {
  188.         string[] asCodes = sCodesRead.Split('|');
  189.         foreach (string sCode in asCodes)
  190.         {
  191.           if (!CodesRead.Contains(sCode))
  192.             CodesRead.Add(sCode);
  193.         }
  194.       }
  195.     }
  196.   }
  197.  
  198.   /// <summary>
  199.   /// Scans one band in the passed bitmap for barcodes.
  200.   /// </summary>
  201.   /// <param name="bmp">Input bitmap</param>
  202.   /// <param name="start">Start coordinate</param>
  203.   /// <param name="end">End coordinate</param>
  204.   /// <param name="direction">
  205.   /// ScanDirection.Vertical: a horizontal band across the page will be examined
  206.   /// and start,end should be valid y-coordinates.
  207.   /// ScanDirection.Horizontal: a vertical band across the page will be examined
  208.   /// and start,end should be valid x-coordinates.
  209.   /// </param>
  210.   /// <param name="types">Barcode types to be found</param>
  211.   /// <returns>Pipe-separated list of barcodes, empty string if none were detected</returns>
  212.   public static string ReadBarcodes(Bitmap bmp, int start, int end, ScanDirection direction, BarcodeType types)
  213.   {
  214.     string sBarCodes = "|"; // will hold return values
  215.  
  216.     // To find a horizontal barcode, find the vertical histogram to find individual barcodes,
  217.     // then get the vertical histogram to decode each
  218.     histogramResult vertHist = verticalHistogram(bmp, start, end, direction);
  219.  
  220.     // Get the light/dark bar patterns.
  221.     // GetBarPatterns returns the bar pattern in 2 formats:
  222.     //
  223.     //   sbCode39Pattern: for Code39 (only distinguishes narrow bars "n" and wide bars "w")
  224.     //   sbEANPattern: for EAN (distinguishes bar widths 1, 2, 3, 4 and L/G-code)
  225.     //
  226.     StringBuilder sbCode39Pattern;
  227.     StringBuilder sbEANPattern;
  228.     GetBarPatterns(ref vertHist, out sbCode39Pattern, out sbEANPattern);
  229.  
  230.     // We now have a barcode in terms of narrow & wide bars... Parse it!
  231.     if ((sbCode39Pattern.Length > 0) || (sbEANPattern.Length > 0))
  232.     {
  233.       for (int iPass = 0; iPass < 2; iPass++)
  234.       {
  235.         if ((types & BarcodeType.Code39) != BarcodeType.None) // if caller wanted Code39
  236.         {
  237.           string sCode39 = ParseCode39(sbCode39Pattern);
  238.           if (sCode39.Length > 0)
  239.             sBarCodes += sCode39 + "|";
  240.         }
  241.         if ((types & BarcodeType.EAN) != BarcodeType.None) // if caller wanted EAN
  242.         {
  243.           string sEAN = ParseEAN(sbEANPattern);
  244.           if (sEAN.Length > 0)
  245.             sBarCodes += sEAN + "|";
  246.         }
  247.         if ((types & BarcodeType.Code128) != BarcodeType.None) // if caller wanted Code128
  248.         {
  249.           // Note: Code128 uses same bar width measurement data as EAN
  250.           string sCode128 = ParseCode128(sbEANPattern);
  251.           if (sCode128.Length > 0)
  252.             sBarCodes += sCode128 + "|";
  253.         }
  254.  
  255.         // Reverse the bar pattern arrays to read again in the mirror direction
  256.         if (iPass == 0)
  257.         {
  258.           sbCode39Pattern = ReversePattern(sbCode39Pattern);
  259.           sbEANPattern = ReversePattern(sbEANPattern);
  260.         }
  261.       }
  262.     }
  263.  
  264.     // Return pipe-separated list of found barcodes, if any
  265.     if (sBarCodes.Length > 2)
  266.       return sBarCodes.Substring(1, sBarCodes.Length - 2);
  267.     return string.Empty;
  268.   }
  269.   #endregion
  270.  
  271.   #region Private functions
  272.  
  273.   #region General
  274.   /// <summary>
  275.   /// Scans for patterns of bars and returns them encoded as strings in the passed
  276.   /// string builder parameters.
  277.   /// </summary>
  278.   /// <param name="hist">Input data containing picture information for the scan line</param>
  279.   /// <param name="sbCode39Pattern">Returns string containing "w" for wide bars and "n" for narrow bars</param>
  280.   /// <param name="sbEANPattern">Returns string with numbers designating relative bar widths compared to
  281.   /// narrowest bar: "1" to "4" are valid widths that can be present in an EAN barcode</param>
  282.   /// <remarks>In both output strings, "|"-characters will be inserted to indicate gaps
  283.   /// in the input data.</remarks>
  284.   private static void GetBarPatterns(ref histogramResult hist, out StringBuilder sbCode39Pattern, out StringBuilder sbEANPattern)
  285.   {
  286.     // Initialize return data
  287.     sbCode39Pattern = new StringBuilder();
  288.     sbEANPattern = new StringBuilder();
  289.  
  290.     if (hist.zones != null) // if barcode zones were found along the scan line
  291.     {
  292.       for (int iZone = 0; iZone < hist.zones.Length; iZone++)
  293.       {
  294.         // Recalculate bar width distribution if more than one zone is present, it could differ per zone
  295.         if (hist.zones.Length > 1)
  296.           GetBarWidthDistribution(ref hist, hist.zones[iZone].Start, hist.zones[iZone].End);
  297.  
  298.         // Check the calculated narrow bar widths. If they are very different, the pattern is
  299.         // unlikely to be a bar code
  300.         if (ValidBars(ref hist))
  301.         {
  302.           // add gap separator to output patterns
  303.           sbCode39Pattern.Append("|");
  304.           sbEANPattern.Append("|");
  305.  
  306.           // Variables needed to check for
  307.           int iBarStart = 0;
  308.           bool bDarkBar = (hist.histogram[0] <= hist.threshold);
  309.  
  310.           // Find the narrow and wide bars
  311.           for (int i = 1; i < hist.histogram.Length; ++i)
  312.           {
  313.             bool bDark = (hist.histogram[i] <= hist.threshold);
  314.             if (bDark != bDarkBar)
  315.             {
  316.               int iBarWidth = i - iBarStart;
  317.               float fNarrowBarWidth = bDarkBar ? hist.darknarrowbarwidth : hist.lightnarrowbarwidth;
  318.               float fWiderBarWidth = bDarkBar ? hist.darkwiderbarwidth : hist.lightwiderbarwidth;
  319.               if (IsWideBar(iBarWidth, fNarrowBarWidth, fWiderBarWidth))
  320.               {
  321.                 // The bar was wider than the narrow bar width, it's a wide bar or a gap
  322.                 if (iBarWidth > GAPFACTOR * fNarrowBarWidth)
  323.                 {
  324.                   sbCode39Pattern.Append("|");
  325.                   sbEANPattern.Append("|");
  326.                 }
  327.                 else
  328.                 {
  329.                   sbCode39Pattern.Append("w");
  330.                   AppendEAN(sbEANPattern, iBarWidth, fNarrowBarWidth);
  331.                 }
  332.               }
  333.               else
  334.               {
  335.                 // The bar is a narrow bar
  336.                 sbCode39Pattern.Append("n");
  337.                 AppendEAN(sbEANPattern, iBarWidth, fNarrowBarWidth);
  338.               }
  339.               bDarkBar = bDark;
  340.               iBarStart = i;
  341.             }
  342.           }
  343.         }
  344.       }
  345.     }
  346.   }
  347.  
  348.   /// <summary>
  349.   /// Returns true if the bar appears to be "wide".
  350.   /// </summary>
  351.   /// <param name="iBarWidth">measured bar width in pixels</param>
  352.   /// <param name="fNarrowBarWidth">average narrow bar width</param>
  353.   /// <param name="fWiderBarWidth">average width of next wider bar</param>
  354.   /// <returns></returns>
  355.   private static bool IsWideBar(int iBarWidth, float fNarrowBarWidth, float fWiderBarWidth)
  356.   {
  357.     if (fNarrowBarWidth < 4.0)
  358.       return (iBarWidth > WIDEFACTOR * fNarrowBarWidth);
  359.     return (iBarWidth >= fWiderBarWidth) || ((fWiderBarWidth - iBarWidth) < (iBarWidth - fNarrowBarWidth));
  360.   }
  361.  
  362.   /// <summary>
  363.   /// Checks if dark and light narrow bar widths are in agreement.
  364.   /// </summary>
  365.   /// <param name="hist">barcode data</param>
  366.   /// <returns>true if barcode data is valid</returns>
  367.   private static bool ValidBars(ref histogramResult hist)
  368.   {
  369.     float fCompNarrowBarWidths = hist.lightnarrowbarwidth / hist.darknarrowbarwidth;
  370.     float fCompWiderBarWidths = hist.lightwiderbarwidth / hist.darkwiderbarwidth;
  371.     return ((fCompNarrowBarWidths >= 0.5) && (fCompNarrowBarWidths <= 2.0)
  372.          && (fCompWiderBarWidths >= 0.5) && (fCompWiderBarWidths <= 2.0)
  373.          && (hist.darkwiderbarwidth / hist.darknarrowbarwidth >= 1.5)
  374.          && (hist.lightwiderbarwidth / hist.lightnarrowbarwidth >= 1.5));
  375.   }
  376.  
  377.   /// <summary>
  378.   /// Used by ReadBarcodes to reverse a bar pattern.
  379.   /// </summary>
  380.   /// <param name="sbPattern">String builder containing a bar pattern string</param>
  381.   /// <returns>String builder containing the reverse of the input string</returns>
  382.   private static StringBuilder ReversePattern(StringBuilder sbPattern)
  383.   {
  384.     if (sbPattern.Length > 0)
  385.     {
  386.       char[] acPattern = sbPattern.ToString().ToCharArray();
  387.       Array.Reverse(acPattern);
  388.       sbPattern = new StringBuilder(acPattern.Length);
  389.       sbPattern.Append(acPattern);
  390.     }
  391.     return sbPattern;
  392.   }
  393.  
  394.   /// <summary>
  395.   /// Vertical histogram of an image
  396.   /// </summary>
  397.   /// <param name="bmp">Bitmap</param>
  398.   /// <param name="start">Start coordinate of band to be scanned</param>
  399.   /// <param name="end">End coordinate of band to be scanned</param>
  400.   /// <param name="direction">
  401.   /// ScanDirection.Vertical: start and end denote y-coordinates.
  402.   /// ScanDirection.Horizontal: start and end denote x-coordinates.
  403.   /// </param>
  404.   /// <returns>histogramResult, containing average brightness values across the scan line</returns>
  405.   private static histogramResult verticalHistogram(Bitmap bmp, int start, int end, ScanDirection direction)
  406.   {
  407.     // convert the pixel format of the bitmap to something that we can handle
  408.     PixelFormat pf = CheckSupportedPixelFormat(bmp.PixelFormat);
  409.     BitmapData bmData;
  410.     int xMax, yMax;
  411.  
  412.     if (direction == ScanDirection.Horizontal)
  413.     {
  414.       bmData = bmp.LockBits(new Rectangle(start, 0, end - start, bmp.Height), ImageLockMode.ReadOnly, pf);
  415.       xMax = bmData.Height;
  416.       yMax = end - start;
  417.     }
  418.     else
  419.     {
  420.       bmData = bmp.LockBits(new Rectangle(0, start, bmp.Width, end - start), ImageLockMode.ReadOnly, pf);
  421.       xMax = bmp.Width;
  422.       yMax = bmData.Height;
  423.     }
  424.  
  425.     // Create the return value
  426.     byte[] histResult = new byte[xMax + 2]; // add 2 to simulate light-colored background pixels at sart and end of scanline
  427.     ushort[] vertSum = new ushort[xMax];
  428.  
  429.     unsafe
  430.     {
  431.       byte* p = (byte*)(void*)bmData.Scan0;
  432.       int stride = bmData.Stride;    // stride is offset between horizontal lines in p
  433.  
  434.       for (int y = 0; y < yMax; ++y)
  435.       {
  436.         // Add up all the pixel values vertically
  437.         for (int x = 0; x < xMax; ++x)
  438.         {
  439.           if (direction == ScanDirection.Horizontal)
  440.             vertSum[x] += getpixelbrightness(p, pf, stride, y, x);
  441.           else
  442.             vertSum[x] += getpixelbrightness(p, pf, stride, x, y);
  443.         }
  444.       }
  445.     }
  446.     bmp.UnlockBits(bmData);
  447.  
  448.     // Now get the average of the row by dividing the pixel by num pixels
  449.     int iDivider = end - start;
  450.     if (pf != PixelFormat.Format1bppIndexed)
  451.       iDivider *= 3;
  452.  
  453.     byte maxValue = byte.MinValue; // Start the max value at zero
  454.     byte minValue = byte.MaxValue; // Start the min value at the absolute maximum
  455.  
  456.     for (int i = 1; i <= xMax; i++) // note: intentionally skips first pixel in histResult
  457.     {
  458.       histResult[i] = (byte)(vertSum[i - 1] / iDivider);
  459.       //Save the max value for later
  460.       if (histResult[i] > maxValue) maxValue = histResult[i];
  461.       // Save the min value for later
  462.       if (histResult[i] < minValue) minValue = histResult[i];
  463.     }
  464.  
  465.     // Set first and last pixel to "white", i.e., maximum intensity
  466.     histResult[0] = maxValue;
  467.     histResult[xMax + 1] = maxValue;
  468.  
  469.     histogramResult retVal = new histogramResult();
  470.     retVal.histogram = histResult;
  471.     retVal.max = maxValue;
  472.     retVal.min = minValue;
  473.  
  474.     // Now we have the brightness distribution along the scan band, try to find the distribution of bar widths.
  475.     retVal.threshold = (byte)(minValue + ((maxValue - minValue) >> 1));
  476.     GetBarWidthDistribution(ref retVal, 0, retVal.histogram.Length);
  477.  
  478.     // Now that we know the narrow bar width, lets look for barcode zones.
  479.     // The image could have more than one barcode in the same band, with
  480.     // different bar widths.
  481.     FindBarcodeZones(ref retVal);
  482.     return retVal;
  483.   }
  484.  
  485.   /// <summary>
  486.   /// Gets the bar width distribution and calculates narrow bar width over the specified
  487.   /// range of the histogramResult. A histogramResult could have multiple ranges, separated
  488.   /// by quiet zones.
  489.   /// </summary>
  490.   /// <param name="hist">histogramResult data</param>
  491.   /// <param name="iStart">start coordinate to be considered</param>
  492.   /// <param name="iEnd">end coordinate + 1</param>
  493.   private static void GetBarWidthDistribution(ref histogramResult hist, int iStart, int iEnd)
  494.   {
  495.     HybridDictionary hdLightBars = new HybridDictionary();
  496.     HybridDictionary hdDarkBars = new HybridDictionary();
  497.     bool bDarkBar = (hist.histogram[iStart] <= hist.threshold);
  498.     int iBarStart = 0;
  499.     for (int i = iStart + 1; i < iEnd; i++)
  500.     {
  501.       bool bDark = (hist.histogram[i] <= hist.threshold);
  502.       if (bDark != bDarkBar)
  503.       {
  504.         int iBarWidth = i - iBarStart;
  505.         if (bDarkBar)
  506.         {
  507.           if (!hdDarkBars.Contains(iBarWidth))
  508.             hdDarkBars.Add(iBarWidth, 1);
  509.           else
  510.             hdDarkBars[iBarWidth] = (int)hdDarkBars[iBarWidth] + 1;
  511.         }
  512.         else
  513.         {
  514.           if (!hdLightBars.Contains(iBarWidth))
  515.             hdLightBars.Add(iBarWidth, 1);
  516.           else
  517.             hdLightBars[iBarWidth] = (int)hdLightBars[iBarWidth] + 1;
  518.         }
  519.         bDarkBar = bDark;
  520.         iBarStart = i;
  521.       }
  522.     }
  523.  
  524.     // Now get the most common bar widths
  525.     CalcNarrowBarWidth(hdLightBars, out hist.lightnarrowbarwidth, out hist.lightwiderbarwidth);
  526.     CalcNarrowBarWidth(hdDarkBars, out hist.darknarrowbarwidth, out hist.darkwiderbarwidth);
  527.   }
  528.  
  529.   private static void CalcNarrowBarWidth(HybridDictionary hdBarWidths, out float fNarrowBarWidth, out float fWiderBarWidth)
  530.   {
  531.     fNarrowBarWidth = 1.0f;
  532.     fWiderBarWidth = 2.0f;
  533.     if (hdBarWidths.Count > 1) // we expect at least two different bar widths in supported barcodes
  534.     {
  535.       int[] aiWidths = new int[hdBarWidths.Count];
  536.       int[] aiCounts = new int[hdBarWidths.Count];
  537.       int i = 0;
  538.       foreach (int iKey in hdBarWidths.Keys)
  539.       {
  540.         aiWidths[i] = iKey;
  541.         aiCounts[i] = (int)hdBarWidths[iKey];
  542.         i++;
  543.       }
  544.       Array.Sort(aiWidths, aiCounts);
  545.  
  546.       // walk from lowest to highest width. The narrowest bar should occur at least 4 times
  547.       fNarrowBarWidth = aiWidths[0];
  548.       fWiderBarWidth = WIDEFACTOR * fNarrowBarWidth;
  549.       for (i = 0; i < aiCounts.Length; i++)
  550.       {
  551.         if (aiCounts[i] >= MINNARROWBARCOUNT)
  552.         {
  553.           fNarrowBarWidth = aiWidths[i];
  554.           if (fNarrowBarWidth < 3)
  555.             fWiderBarWidth = WIDEFACTOR * fNarrowBarWidth;
  556.           else
  557.           {
  558.             // if the width is not singular, look for the most common width in the neighbourhood
  559.             float fCount;
  560.             FindPeakWidth(i, ref aiWidths, ref aiCounts, out fNarrowBarWidth, out fCount);
  561.             fWiderBarWidth = WIDEFACTOR * fNarrowBarWidth;
  562.  
  563.             if (fNarrowBarWidth >= 6)
  564.             {
  565.               // ... and for the next wider common bar width if the barcode is fairly large
  566.               float fMaxCount = 0.0f;
  567.               for (int j = i + 1; j < aiCounts.Length; j++)
  568.               {
  569.                 float fNextWidth, fNextCount;
  570.                 FindPeakWidth(j, ref aiWidths, ref aiCounts, out fNextWidth, out fNextCount);
  571.                 if (fNextWidth / fNarrowBarWidth > 1.5)
  572.                 {
  573.                   if (fNextCount > fMaxCount)
  574.                   {
  575.                     fWiderBarWidth = fNextWidth;
  576.                     fMaxCount = fNextCount;
  577.                   }
  578.                   else
  579.                     break;
  580.                 }
  581.               }
  582.             }
  583.           }
  584.           break;
  585.         }
  586.       }
  587.     }
  588.   }
  589.  
  590.   static void FindPeakWidth(int i, ref int[] aiWidths, ref int[] aiCounts, out float fWidth, out float fCount)
  591.   {
  592.     fWidth = 0.0f;
  593.     fCount = 0.0f;
  594.     int iSamples = 0;
  595.     for (int j = i - 1; j <= i + 1; j++)
  596.     {
  597.       if ((j >= 0) && (j < aiWidths.Length) && (Math.Abs(aiWidths[j] - aiWidths[i]) == Math.Abs(j - i)))
  598.       {
  599.         iSamples++;
  600.         fCount += aiCounts[j];
  601.         fWidth += aiWidths[j] * aiCounts[j];
  602.       }
  603.     }
  604.     fWidth /= fCount;
  605.     fCount /= iSamples;
  606.   }
  607.  
  608.   /// <summary>
  609.   /// FindBarcodeZones looks for barcode zones in the current band.
  610.   /// We look for white space that is more than GAPFACTOR * narrowbarwidth
  611.   /// separating two zones. For narrowbarwidth we take the maximum of the
  612.   /// dark and light narrow bar width.
  613.   /// </summary>
  614.   /// <param name="hist">Data for current image band</param>
  615.   private static void FindBarcodeZones(ref histogramResult hist)
  616.   {
  617.     if (!ValidBars(ref hist))
  618.       hist.zones = null;
  619.     else if (!UseBarcodeZones)
  620.     {
  621.       hist.zones = new BarcodeZone[1];
  622.       hist.zones[0].Start = 0;
  623.       hist.zones[0].End = hist.histogram.Length;
  624.     }
  625.     else
  626.     {
  627.       ArrayList alBarcodeZones = new ArrayList();
  628.       bool bDarkBar = (hist.histogram[0] <= hist.threshold);
  629.       int iBarStart = 0;
  630.       int iZoneStart = -1;
  631.       int iZoneEnd = -1;
  632.       float fQuietZoneWidth = GAPFACTOR * (hist.darknarrowbarwidth + hist.lightnarrowbarwidth) / 2;
  633.       float fMinZoneWidth = fQuietZoneWidth;
  634.  
  635.       for (int i = 1; i < hist.histogram.Length; i++)
  636.       {
  637.         bool bDark = (hist.histogram[i] <= hist.threshold);
  638.         if (bDark != bDarkBar)
  639.         {
  640.           int iBarWidth = i - iBarStart;
  641.           if (!bDarkBar) // This ends a light area
  642.           {
  643.             if ((iZoneStart == -1) || (iBarWidth > fQuietZoneWidth))
  644.             {
  645.               // the light area can be seen as a quiet zone
  646.               iZoneEnd = i - (iBarWidth >> 1);
  647.  
  648.               // Check if the active zone is big enough to contain a barcode
  649.               if ((iZoneStart >= 0) && (iZoneEnd > iZoneStart + fMinZoneWidth))
  650.               {
  651.                 // record the barcode zone that ended in the detected quiet zone ...
  652.                 BarcodeZone bz = new BarcodeZone();
  653.                 bz.Start = iZoneStart;
  654.                 bz.End = iZoneEnd;
  655.                 alBarcodeZones.Add(bz);
  656.  
  657.                 // .. and start a new barcode zone
  658.                 iZoneStart = iZoneEnd;
  659.               }
  660.               if (iZoneStart == -1)
  661.                 iZoneStart = iZoneEnd; // first zone starts here
  662.             }
  663.           }
  664.           bDarkBar = bDark;
  665.           iBarStart = i;
  666.         }
  667.       }
  668.       if (iZoneStart >= 0)
  669.       {
  670.         BarcodeZone bz = new BarcodeZone();
  671.         bz.Start = iZoneStart;
  672.         bz.End = hist.histogram.Length;
  673.         alBarcodeZones.Add(bz);
  674.       }
  675.       if (alBarcodeZones.Count > 0)
  676.         hist.zones = (BarcodeZone[])alBarcodeZones.ToArray(typeof(BarcodeZone));
  677.       else
  678.         hist.zones = null;
  679.     }
  680.   }
  681.  
  682.   /// <summary>
  683.   /// Checks if the supplied pixelFormat is supported, returns the default
  684.   /// pixel format (PixelFormat.Format24bppRgb) if it isn't supported.
  685.   /// </summary>
  686.   /// <param name="pixelFormat">Input pixel format</param>
  687.   /// <returns>Input pixel format if it is supported, else default.</returns>
  688.   private static PixelFormat CheckSupportedPixelFormat(PixelFormat pixelFormat)
  689.   {
  690.     switch (pixelFormat)
  691.     {
  692.       case PixelFormat.Format1bppIndexed:
  693.       case PixelFormat.Format32bppArgb:
  694.       case PixelFormat.Format32bppRgb:
  695.         return pixelFormat;
  696.     }
  697.     return PixelFormat.Format24bppRgb;
  698.   }
  699.  
  700.   /// <summary>
  701.   /// Calculates pixel brightness for specified pixel in byte array of locked bitmap rectangle.
  702.   /// For RGB  : returns sum of the three color values.
  703.   /// For 1bpp : returns 255 for a white pixel, 0 for a black pixel.
  704.   /// </summary>
  705.   /// <param name="p">Byte array containing pixel information</param>
  706.   /// <param name="pf">Pixel format used in the byte array</param>
  707.   /// <param name="stride">Byte offset between scan lines</param>
  708.   /// <param name="x">Horizontal coordinate, relative to upper left corner of locked rectangle</param>
  709.   /// <param name="y">Vertical coordinate, relative to upper left corner of locked rectangle</param>
  710.   /// <returns></returns>
  711.   private static unsafe ushort getpixelbrightness(byte* p, PixelFormat pf, int stride, int x, int y)
  712.   {
  713.     ushort uBrightness = 0;
  714.     switch (pf)
  715.     {
  716.       case PixelFormat.Format1bppIndexed:
  717.         byte b = p[(y * stride) + (x >> 3)];
  718.         if (((b << (x % 8)) & 128) != 0)
  719.           uBrightness = 255;
  720.         break;
  721.  
  722.       default: // 24bpp RGB, 32bpp formats
  723.         int iByte = (y * stride) + (x * (pf == PixelFormat.Format24bppRgb ? 3 : 4));
  724.         for (int i = iByte; i < iByte + 3; i++)
  725.           uBrightness += p[i];
  726.         break;
  727.     }
  728.     return uBrightness;
  729.   }
  730.   #endregion
  731.  
  732.   #region Code39-specific
  733.   /// <summary>
  734.   /// Parses Code39 barcodes from the input pattern.
  735.   /// </summary>
  736.   /// <param name="sbPattern">Input pattern, should contain "n"-characters to
  737.   /// indicate narrow bars and "w" to indicate wide bars.</param>
  738.   /// <returns>Pipe-separated list of barcodes, empty string if none were detected</returns>
  739.   private static string ParseCode39(StringBuilder sbPattern)
  740.   {
  741.     // Each pattern within code 39 is nine bars with one white bar between each pattern
  742.     if (sbPattern.Length > 9)
  743.     {
  744.       StringBuilder sbBarcodes = new StringBuilder();
  745.       string sPattern = sbPattern.ToString();
  746.       int iStarPattern = sPattern.IndexOf(STARPATTERN); // index of first star barcode in pattern
  747.       while ((iStarPattern >= 0) && (iStarPattern <= sbPattern.Length - 9))
  748.       {
  749.         int iPos = iStarPattern;
  750.         int iNoise = 0;
  751.         StringBuilder sbData = new StringBuilder((int)(sbPattern.Length / 10));
  752.         while (iPos <= sbPattern.Length - 9)
  753.         {
  754.           // Test the next 9 characters from the pattern string
  755.           string sData = ParseCode39Pattern(sbPattern.ToString(iPos, 9));
  756.  
  757.           if (sData == null) // no recognizeable data
  758.           {
  759.             iPos++;
  760.             iNoise++;
  761.           }
  762.           else
  763.           {
  764.             // record if the data contained a lot of noise before the next valid data
  765.             if (iNoise >= 2)
  766.               sbData.Append("|");
  767.             iNoise = 0; // reset noise counter
  768.             sbData.Append(sData);
  769.             iPos += 10;
  770.           }
  771.         }
  772.         if (sbData.Length > 0)
  773.         {
  774.           // look for valid Code39 patterns in the data.
  775.           // A valid Code39 pattern starts and ends with "*" and does not contain a noise character "|".
  776.           // We return a pipe-separated list of these patterns.
  777.           string[] asBarcodes = sbData.ToString().Split('|');
  778.           foreach (string sBarcode in asBarcodes)
  779.           {
  780.             if (sBarcode.Length > 2)
  781.             {
  782.               int iFirstStar = sBarcode.IndexOf("*");
  783.               if ((iFirstStar >= 0) && (iFirstStar < sBarcode.Length - 1))
  784.               {
  785.                 int iSecondStar = sBarcode.IndexOf("*", iFirstStar + 1);
  786.                 if (iSecondStar > iFirstStar + 1)
  787.                 {
  788.                   sbBarcodes.Append(sBarcode.Substring(iFirstStar + 1, (iSecondStar - iFirstStar - 1)) + "|");
  789.                 }
  790.               }
  791.             }
  792.           }
  793.         }
  794.         iStarPattern = sPattern.IndexOf(STARPATTERN, iStarPattern + 5); // "nwnnwnwnn" pattern can not occur again before current index + 5
  795.       }
  796.       if (sbBarcodes.Length > 1)
  797.         return sbBarcodes.ToString(0, sbBarcodes.Length - 1);
  798.     }
  799.     return string.Empty;
  800.   }
  801.  
  802.   /// <summary>
  803.   /// Parses bar pattern for one Code39 character.
  804.   /// </summary>
  805.   /// <param name="pattern">Pattern to be examined, should be 9 characters</param>
  806.   /// <returns>Detected character or null</returns>
  807.   private static string ParseCode39Pattern(string pattern)
  808.   {
  809.     switch (pattern)
  810.     {
  811.       case "wnnwnnnnw":
  812.         return "1";
  813.       case "nnwwnnnnw":
  814.         return "2";
  815.       case "wnwwnnnnn":
  816.         return "3";
  817.       case "nnnwwnnnw":
  818.         return "4";
  819.       case "wnnwwnnnn":
  820.         return "5";
  821.       case "nnwwwnnnn":
  822.         return "6";
  823.       case "nnnwnnwnw":
  824.         return "7";
  825.       case "wnnwnnwnn":
  826.         return "8";
  827.       case "nnwwnnwnn":
  828.         return "9";
  829.       case "nnnwwnwnn":
  830.         return "0";
  831.       case "wnnnnwnnw":
  832.         return "A";
  833.       case "nnwnnwnnw":
  834.         return "B";
  835.       case "wnwnnwnnn":
  836.         return "C";
  837.       case "nnnnwwnnw":
  838.         return "D";
  839.       case "wnnnwwnnn":
  840.         return "E";
  841.       case "nnwnwwnnn":
  842.         return "F";
  843.       case "nnnnnwwnw":
  844.         return "G";
  845.       case "wnnnnwwnn":
  846.         return "H";
  847.       case "nnwnnwwnn":
  848.         return "I";
  849.       case "nnnnwwwnn":
  850.         return "J";
  851.       case "wnnnnnnww":
  852.         return "K";
  853.       case "nnwnnnnww":
  854.         return "L";
  855.       case "wnwnnnnwn":
  856.         return "M";
  857.       case "nnnnwnnww":
  858.         return "N";
  859.       case "wnnnwnnwn":
  860.         return "O";
  861.       case "nnwnwnnwn":
  862.         return "P";
  863.       case "nnnnnnwww":
  864.         return "Q";
  865.       case "wnnnnnwwn":
  866.         return "R";
  867.       case "nnwnnnwwn":
  868.         return "S";
  869.       case "nnnnwnwwn":
  870.         return "T";
  871.       case "wwnnnnnnw":
  872.         return "U";
  873.       case "nwwnnnnnw":
  874.         return "V";
  875.       case "wwwnnnnnn":
  876.         return "W";
  877.       case "nwnnwnnnw":
  878.         return "X";
  879.       case "wwnnwnnnn":
  880.         return "Y";
  881.       case "nwwnwnnnn":
  882.         return "Z";
  883.       case "nwnnnnwnw":
  884.         return "-";
  885.       case "wwnnnnwnn":
  886.         return ".";
  887.       case "nwwnnnwnn":
  888.         return " ";
  889.       case STARPATTERN:
  890.         return "*";
  891.       case "nwnwnwnnn":
  892.         return "$";
  893.       case "nwnwnnnwn":
  894.         return "/";
  895.       case "nwnnnwnwn":
  896.         return "+";
  897.       case "nnnwnwnwn":
  898.         return "%";
  899.       default:
  900.         return null;
  901.     }
  902.   }
  903.   #endregion
  904.  
  905.   #region EAN-specific
  906.   /// <summary>
  907.   /// Parses EAN-barcodes from the input pattern.
  908.   /// </summary>
  909.   /// <param name="sbPattern">Input pattern, should contain characters
  910.   /// "1" .. "4" to indicate valid EAN bar widths.</param>
  911.   /// <returns>Pipe-separated list of barcodes, empty string if none were detected</returns>
  912.   private static string ParseEAN(StringBuilder sbPattern)
  913.   {
  914.     StringBuilder sbEANData = new StringBuilder(32);
  915.     int iEANSeparators = 0;
  916.     string sEANCode = string.Empty;
  917.  
  918.     int iPos = 0;
  919.     sbPattern.Append("|"); // append one extra "gap" character because separator has only 3 bands
  920.     while (iPos <= sbPattern.Length - 4)
  921.     {
  922.       string sEANDigit = ParseEANPattern(sbPattern.ToString(iPos, 4), sEANCode, iEANSeparators);
  923.       switch (sEANDigit)
  924.       {
  925.         case null:
  926.           // reset on invalid code
  927.           //iEANSeparators = 0;
  928.           sEANCode = string.Empty;
  929.           iPos++;
  930.           break;
  931.         case "|":
  932.           // EAN separator found. Each EAN code contains three separators.
  933.           if (iEANSeparators >= 3)
  934.             iEANSeparators = 1;
  935.           else
  936.             iEANSeparators++;
  937.           iPos += 3;
  938.           if (iEANSeparators == 2)
  939.           {
  940.             iPos += 2; // middle separator has 5 bars
  941.           }
  942.           else if (iEANSeparators == 3) // end of EAN code detected
  943.           {
  944.             string sFirstDigit = GetEANFirstDigit(ref sEANCode);
  945.             if ((sFirstDigit != null) && (sEANCode.Length == 12))
  946.             {
  947.               sEANCode = sFirstDigit + sEANCode;
  948.               if (sbEANData.Length > 0)
  949.                 sbEANData.Append("|");
  950.               sbEANData.Append(sEANCode);
  951.             }
  952.             // reset after end of code
  953.             //iEANSeparators = 0;
  954.             sEANCode = string.Empty;
  955.           }
  956.           break;
  957.         case "S":
  958.           // Start of supplemental code after EAN code
  959.           iPos += 3;
  960.           sEANCode = "S";
  961.           iEANSeparators = 1;
  962.           break;
  963.         default:
  964.           if (iEANSeparators > 0)
  965.           {
  966.             sEANCode += sEANDigit;
  967.             iPos += 4;
  968.             if (sEANCode.StartsWith("S"))
  969.             {
  970.               // Each digit of the supplemental code is followed by an additional "11"
  971.               // We assume that the code ends if that is no longer the case.
  972.               if ((sbPattern.Length > iPos + 2) && (sbPattern.ToString(iPos, 2) == "11"))
  973.                 iPos += 2;
  974.               else
  975.               {
  976.                 // Supplemental code ends. It must be either 2 or 5 digits.
  977.                 sEANCode = CheckEANSupplement(sEANCode);
  978.                 if (sEANCode.Length > 0)
  979.                 {
  980.                   if (sbEANData.Length > 0)
  981.                     sbEANData.Append("|");
  982.                   sbEANData.Append(sEANCode);
  983.                 }
  984.                 // reset after end of code
  985.                 iEANSeparators = 0;
  986.                 sEANCode = string.Empty;
  987.               }
  988.             }
  989.           }
  990.           else
  991.             iPos++; // no EAN digit expected before first separator
  992.           break;
  993.       }
  994.     }
  995.     return sbEANData.ToString();
  996.   }
  997.  
  998.   /// <summary>
  999.   /// Used by GetBarPatterns to derive bar character from bar width.
  1000.   /// </summary>
  1001.   /// <param name="sbEAN">Output pattern</param>
  1002.   /// <param name="nBarWidth">Measured bar width in pixels</param>
  1003.   /// <param name="fNarrowBarWidth">Narrow bar width in pixels</param>
  1004.   private static void AppendEAN(StringBuilder sbEAN, int nBarWidth, float fNarrowBarWidth)
  1005.   {
  1006.     int nEAN = (int)Math.Round((double)nBarWidth / fNarrowBarWidth);
  1007.     if (nEAN == 5) nEAN = 4; // bar width could be slightly off due to distortion
  1008.     if (nEAN < 10)
  1009.       sbEAN.Append(nEAN.ToString());
  1010.     else
  1011.       sbEAN.Append("|");
  1012.   }
  1013.  
  1014.   /// <summary>
  1015.   /// Parses the EAN pattern for one digit or separator
  1016.   /// </summary>
  1017.   /// <param name="sPattern">Pattern to be parsed</param>
  1018.   /// <param name="sEANCode">EAN code found so far</param>
  1019.   /// <param name="iEANSeparators">Number of separators found so far</param>
  1020.   /// <returns>Detected digit type (L/G/R) and digit, "|" for separator
  1021.   /// or null if the pattern was not recognized.</returns>
  1022.   private static string ParseEANPattern(string sPattern, string sEANCode, int iEANSeparators)
  1023.   {
  1024.     string[] LRCodes =
  1025.         {"3211", "2221", "2122", "1411", "1132",
  1026.          "1231", "1114", "1312", "1213", "3112"};
  1027.     string[] GCodes =
  1028.         {"1123", "1222", "2212", "1141", "2311",
  1029.          "1321", "4111", "2131", "3121", "2113"};
  1030.     if ((sPattern != null) && (sPattern.Length >= 3))
  1031.     {
  1032.       if (sPattern.StartsWith("111") && ((iEANSeparators * 12) == sEANCode.Length))
  1033.         return "|";   // found separator
  1034.       if (sPattern.StartsWith("112") && (iEANSeparators == 3) && (sEANCode.Length == 0))
  1035.         return "S";   // found EAN supplemental code
  1036.  
  1037.       for (int i = 0; i < 10; i++)
  1038.       {
  1039.         if (sPattern.StartsWith(LRCodes[i]))
  1040.           return ((iEANSeparators == 2) ? "R" : "L") + i.ToString();
  1041.         if (sPattern.StartsWith(GCodes[i]))
  1042.           return "G" + i.ToString();
  1043.       }
  1044.     }
  1045.     return null;
  1046.   }
  1047.  
  1048.   /// <summary>
  1049.   /// Decodes the L/G-pattern for the left half of the EAN code
  1050.   /// to derive the first digit. See table in
  1051.   /// http://en.wikipedia.org/wiki/European_Article_Number
  1052.   /// </summary>
  1053.   /// <param name="sEANPattern">
  1054.   /// IN: EAN pattern with digits and L/G/R codes.
  1055.   /// OUT: EAN digits only.
  1056.   /// </param>
  1057.   /// <returns>Detected first digit or null.</returns>
  1058.   private static string GetEANFirstDigit(ref string sEANPattern)
  1059.   {
  1060.     string[] LGPatterns =
  1061.         {"LLLLLL", "LLGLGG", "LLGGLG", "LLGGGL", "LGLLGG",
  1062.          "LGGLLG", "LGGGLL", "LGLGLG", "LGLGGL", "LGGLGL"};
  1063.     string sLG = string.Empty;
  1064.     string sDigits = string.Empty;
  1065.     if ((sEANPattern != null) && (sEANPattern.Length >= 24))
  1066.     {
  1067.       for (int i = 0; i < 12; i++)
  1068.       {
  1069.         sLG += sEANPattern[2 * i];
  1070.         sDigits += sEANPattern[2 * i + 1];
  1071.       }
  1072.       for (int i = 0; i < 10; i++)
  1073.       {
  1074.         if (sLG.StartsWith(LGPatterns[i]))
  1075.         {
  1076.           sEANPattern = sDigits + sEANPattern.Substring(24);
  1077.           return i.ToString();
  1078.         }
  1079.       }
  1080.     }
  1081.     return null;
  1082.   }
  1083.  
  1084.   /// <summary>
  1085.   /// Checks if EAN supplemental code is valid.
  1086.   /// </summary>
  1087.   /// <param name="sEANPattern">Parse result</param>
  1088.   /// <returns>Supplemental code or empty string</returns>
  1089.   private static string CheckEANSupplement(string sEANPattern)
  1090.   {
  1091.     try
  1092.     {
  1093.       if (sEANPattern.StartsWith("S"))
  1094.       {
  1095.         string sDigits = string.Empty;
  1096.         string sLG = string.Empty;
  1097.         for (int i = 1; i < sEANPattern.Length - 1; i += 2)
  1098.         {
  1099.           sLG += sEANPattern[i];
  1100.           sDigits += sEANPattern[i + 1];
  1101.         }
  1102.  
  1103.         // Supplemental code must be either 2 or 5 digits.
  1104.         switch (sDigits.Length)
  1105.         {
  1106.           case 2:
  1107.             // Do EAN-2 parity check
  1108.             string[] EAN2Parity = { "LL", "LG", "GL", "GG" };
  1109.             int iParity = Convert.ToInt32(sDigits) % 4;
  1110.             if (sLG != EAN2Parity[iParity])
  1111.               return string.Empty; // parity check failed
  1112.             break;
  1113.           case 5:
  1114.             // Do EAN-5 checksum validation
  1115.             uint uCheckSum = 0;
  1116.             for (int i = 0; i < sDigits.Length; i++)
  1117.             {
  1118.               uCheckSum += (uint)(Convert.ToUInt32(sDigits.Substring(i, 1)) * (((i & 1) == 0) ? 3 : 9));
  1119.             }
  1120.             string[] EAN5CheckSumPattern =
  1121.                 {"GGLLL", "GLGLL", "GLLGL", "GLLLG", "LGGLL",
  1122.                  "LLGGL", "LLLGG", "LGLGL", "LGLLG", "LLGLG"};
  1123.             if (sLG != EAN5CheckSumPattern[uCheckSum % 10])
  1124.               return string.Empty; // Checksum validation failed
  1125.             break;
  1126.           default:
  1127.             return string.Empty;
  1128.         }
  1129.         return "S" + sDigits;
  1130.       }
  1131.     }
  1132.     catch (Exception ex)
  1133.     {
  1134.       System.Diagnostics.Trace.Write(ex);
  1135.     }
  1136.     return string.Empty;
  1137.   }
  1138.   #endregion
  1139.  
  1140.   #region Code128-specific
  1141.   /// <summary>
  1142.   /// Parses Code128 barcodes.
  1143.   /// </summary>
  1144.   /// <param name="sbPattern">Input pattern, should contain characters
  1145.   /// "1" .. "4" to indicate valid bar widths.</param>
  1146.   /// <returns>Pipe-separated list of barcodes, empty string if none were detected</returns>
  1147.   private static string ParseCode128(StringBuilder sbPattern)
  1148.   {
  1149.     StringBuilder sbCode128Data = new StringBuilder(32);
  1150.     string sCode128Code = string.Empty;
  1151.     uint uCheckSum = 0;
  1152.     int iCodes = 0;
  1153.     int iPos = 0;
  1154.     char cCodePage = 'B';
  1155.     while (iPos <= sbPattern.Length - 6)
  1156.     {
  1157.       int iResult = ParseCode128Pattern(sbPattern.ToString(iPos, 6), ref sCode128Code, ref uCheckSum, ref cCodePage, ref iCodes);
  1158.       switch (iResult)
  1159.       {
  1160.         case -1: // unrecognized pattern
  1161.           iPos++;
  1162.           break;
  1163.         case -2: // stop condition, but failed to recognize barcode
  1164.           iPos += 7;
  1165.           break;
  1166.         case CODE128STOP:
  1167.           iPos += 7;
  1168.           if (sCode128Code.Length > 0)
  1169.           {
  1170.             if (sbCode128Data.Length > 0)
  1171.               sbCode128Data.Append("|");
  1172.             sbCode128Data.Append(sCode128Code);
  1173.           }
  1174.           break;
  1175.         default:
  1176.           iPos += 6;
  1177.           break;
  1178.       }
  1179.     }
  1180.     return sbCode128Data.ToString();
  1181.   }
  1182.  
  1183.   /// <summary>
  1184.   /// Parses the Code128 pattern for one barcode character.
  1185.   /// </summary>
  1186.   /// <param name="sPattern">Pattern to be parsed, should be 6 characters</param>
  1187.   /// <param name="sResult">Resulting barcode up to current character</param>
  1188.   /// <param name="uCheckSum">Checksum up to current character</param>
  1189.   /// <param name="cCodePage">Current code page</param>
  1190.   /// <param name="iCodes">Count of barcode characters already parsed (needed for checksum)</param>
  1191.   /// <returns>
  1192.   /// CODE128STOP: end of barcode detected, barcode recognized.
  1193.   ///          -2: end of barcode, recognition failed.
  1194.   ///          -1: unrecognized pattern.
  1195.   ///       other: code 128 character index
  1196.   /// </returns>
  1197.   private static int ParseCode128Pattern(string sPattern, ref string sResult, ref uint uCheckSum, ref char cCodePage, ref int iCodes)
  1198.   {
  1199.     string[] Code128 =
  1200.         {"212222", "222122", "222221", "121223", "121322", "131222",
  1201.          "122213", "122312", "132212", "221213", "221312", "231212",
  1202.          "112232", "122132", "122231", "113222", "123122", "123221",
  1203.          "223211", "221132", "221231", "213212", "223112", "312131",
  1204.          "311222", "321122", "321221", "312212", "322112", "322211",
  1205.          "212123", "212321", "232121", "111323", "131123", "131321",
  1206.          "112313", "132113", "132311", "211313", "231113", "231311",
  1207.          "112133", "112331", "132131", "113123", "113321", "133121",
  1208.          "313121", "211331", "231131", "213113", "213311", "213131",
  1209.          "311123", "311321", "331121", "312113", "312311", "332111",
  1210.          "314111", "221411", "431111", "111224", "111422", "121124",
  1211.          "121421", "141122", "141221", "112214", "112412", "122114",
  1212.          "122411", "142112", "142211", "241211", "221114", "413111",
  1213.          "241112", "134111", "111242", "121142", "121241", "114212",
  1214.          "124112", "124211", "411212", "421112", "421211", "212141",
  1215.          "214121", "412121", "111143", "111341", "131141", "114113",
  1216.          "114311", "411113", "411311", "113141", "114131", "311141",
  1217.          "411131", "211412", "211214", "211232", "233111"};
  1218.  
  1219.     if ((sPattern != null) && (sPattern.Length >= 6))
  1220.     {
  1221.       for (int i = 0; i < Code128.Length; i++)
  1222.       {
  1223.         if (sPattern.StartsWith(Code128[i]))
  1224.         {
  1225.           if (i == CODE128STOP)
  1226.           {
  1227.             try
  1228.             {
  1229.               int iLength = sResult.Length;
  1230.               if (iLength > 1)
  1231.               {
  1232.                 char cCheckDigit;
  1233.                 if (cCodePage == 'C')
  1234.                 {
  1235.                   cCheckDigit = (char)(Convert.ToInt32(sResult.Substring(iLength - 2)) + 32);
  1236.                   sResult = sResult.Substring(0, iLength - 2);
  1237.                 }
  1238.                 else
  1239.                 {
  1240.                   cCheckDigit = sResult[iLength - 1];
  1241.                   sResult = sResult.Substring(0, iLength - 1);
  1242.                 }
  1243.                 uint uCheckDigit = (uint)((int)(((int)cCheckDigit) - 32) * iCodes);
  1244.                 if (uCheckSum > uCheckDigit)
  1245.                 {
  1246.                   uCheckSum = (uCheckSum - uCheckDigit) % 103;
  1247.                   if (cCheckDigit == (char)((int)(uCheckSum + 32)))
  1248.                   {
  1249.                     return CODE128STOP;
  1250.                   }
  1251.                 }
  1252.               }
  1253.             }
  1254.             catch (Exception ex)
  1255.             {
  1256.               System.Diagnostics.Trace.Write(ex);
  1257.             }
  1258.             // If reach this point, some check failed.
  1259.             // Reset everything and return error.
  1260.             sResult = string.Empty;
  1261.             uCheckSum = 0;
  1262.             iCodes = 0;
  1263.             return -2;
  1264.           }
  1265.           else if (i >= CODE128START)
  1266.           {
  1267.             // Start new code 128 sequence
  1268.             sResult = string.Empty;
  1269.             uCheckSum = (uint)i;
  1270.             cCodePage = (char)('A' + (i - CODE128START));
  1271.           }
  1272.           else if (uCheckSum > 0)
  1273.           {
  1274.             bool bSkip = false;
  1275.             char cNewCodePage = cCodePage;
  1276.             switch (i)
  1277.             {
  1278.               case CODE128C:
  1279.                 cNewCodePage = 'C';
  1280.                 break;
  1281.               case CODE128B:
  1282.                 cNewCodePage = 'B';
  1283.                 break;
  1284.               case CODE128A:
  1285.                 cNewCodePage = 'A';
  1286.                 break;
  1287.             }
  1288.             if (cCodePage != cNewCodePage)
  1289.             {
  1290.               cCodePage = cNewCodePage;
  1291.               bSkip = true;
  1292.             }
  1293.             if (!bSkip)
  1294.             {
  1295.               switch (cCodePage)
  1296.               {
  1297.                 case 'C':
  1298.                   sResult += i.ToString("00");
  1299.                   break;
  1300.                 default:
  1301.                   // Regular character
  1302.                   char c = (char)(i + 32);
  1303.                   sResult += c;
  1304.                   break;
  1305.               }
  1306.             }
  1307.             iCodes++;
  1308.             uCheckSum += (uint)(i * iCodes);
  1309.           }
  1310.           return i;
  1311.         }
  1312.       }
  1313.     }
  1314.     sResult = string.Empty;
  1315.     uCheckSum = 0;
  1316.     iCodes = 0;
  1317.     return -1;
  1318.   }
  1319.   #endregion
  1320.  
  1321.   #endregion
  1322. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement