C#でZXingを使って連結QRコードを作成する


ZXing.NETを使用して、C#およびWPF環境で連結QRコードを作成する方法を知りたいです。  
私はZXingを使用してQRコードを作成できることに注意してください。以下は私のC#コードです。

[code]

        string ss = "9MA0GCSqGSIb3DQEBCwUABIIBABVim6AYRyf6AmkZFdAkKvaAvMkQEGYVwa2KUz/FBeu";
        int stCount = ss.Length;
        var qrcodeWriter = new BarcodeWriter();
    
        qrcodeWriter.Format = BarcodeFormat.QR_CODE;
    
        QrCodeEncodingOptions options = new QrCodeEncodingOptions()
        {
            DisableECI = true, // ​​Set the content encoding.
            CharacterSet = "UTF-8", // Set the width and height of the QR code.
            Width = 600,
            Height = 600,
            Margin = 1 // Set the margin of the QR code, the unit is not a fixed pixel.
        };
        qrcodeWriter.Write(ss)
            .Save(@"C:\Demo_Project_Barcode\QRImage\" + $"{txtFileName.Text}.png");
    
[/code]

これにより、QRコードを作成できることがわかっています。また、以下のサイトを参考にして、連結QRコードを作ってみました。

  * [Zxingを使って連結QRコードを読み書きする Part3:連結QRコードの出力 | Program Resource](https://programresource.net/2013/05/04/2193.html)

しかし、これはJavaを使っています。そのため、C#では利用できません。

私は次のようにしてZXing.NETの`QRCodeWriter`クラスにコードを追加しました。

[code]

        public sealed class QRCodeWriter : Writer
        {
            private const int QUIET_ZONE_SIZE = 4;
    
            // Rifat Code Adding New
            public BitMatrix encode_sa(byte page, byte totalpage, int parity,
                String contents, BarcodeFormat format,
                int width, int height, IDictionary<EncodeHintType, object> hints)
            {
                if (contents.Length == 0)
                {
                    throw new ArgumentException("Found empty contents");
                }
                if (format != BarcodeFormat.QR_CODE)
                {
                    //throw new IllegalArgumentException("Can only encode QR_CODE, but got " +format);
                    throw new ArgumentException("Can only encode QR_CODE, but got " + format);
                }
    
                if (width < 0 || height < 0)
                {
                    throw new ArgumentException("Requested dimensions are too small: " + width + 'x' + height);
                }
                ErrorCorrectionLevel errorCorrectionLevel = ErrorCorrectionLevel.L;
                if (hints != null)
                {
                    // ErrorCorrectionLevel requestedECLevel = (ErrorCorrectionLevel)hints.
                    //  ErrorCorrectionLevel requestedECLevel = (ErrorCorrectionLevel)hints.get(EncodeHintType.ERROR_CORRECTION);
                    var requestedECLevel = (ErrorCorrectionLevel)hints[EncodeHintType.ERROR_CORRECTION];
    
                    if (requestedECLevel != null)
                    {
                        errorCorrectionLevel = requestedECLevel;
                    }
                }
                int quietZone = QUIET_ZONE_SIZE;
                if (hints.ContainsKey(EncodeHintType.MARGIN))
                {
                    var quietZoneInt = hints[EncodeHintType.MARGIN];
                    if (quietZoneInt != null)
                    {
                        quietZone = Convert.ToInt32(quietZoneInt.ToString());
                    }
                }
    
                QRCode code = new QRCode();
                Encoder.encode_sa(page, totalpage, parity, contents, errorCorrectionLevel, hints, code);
                return renderResult(code, width, height, quietZone);
            }
    
[/code]

また、次のようにして`Encoder`クラスにコードを追加しました。

[code]

        public static class Encoder
        {
            public static void encode_sa(byte page, byte totalpage, int parity, String content,
                               ErrorCorrectionLevel ecLevel,
                               IDictionary<EncodeHintType, object> hints,
                               QRCode qrCode) //throws WriterException
            {
    
                String encoding = hints == null ? null : (String)hints[EncodeHintType.CHARACTER_SET];
                if (encoding == null)
                {
                    encoding = DEFAULT_BYTE_MODE_ENCODING;
                }
    
                // Step 1: Choose the mode (encoding).
                Mode mode = chooseMode(content, encoding);
    
                // Step 2: Append "bytes" into "dataBits" in appropriate encoding.
                BitArray dataBits = new BitArray();
    
                appendBytes(content, mode, dataBits, encoding);
                // Step 3: Initialize QR code that can contain "dataBits".
                int numInputBits = dataBits.Size;
                //initQRCode(numInputBits, ecLevel, mode, qrCode);
                // I don't know how to initialize it. I think I have to fix it from here.
    
                // Step 4: Build another bit vector that contains header and data.
                BitArray headerAndDataBits = new BitArray();
    
                // Step 4.1: Add code page info
                byte tmpbyte;
                tmpbyte = 0x3;
                headerAndDataBits.appendBits(tmpbyte, 4);
                headerAndDataBits.appendBits(page, 4);
                headerAndDataBits.appendBits(totalpage, 4);
                headerAndDataBits.appendBits((byte)((parity & 0xf0) >> 4), 4);
                headerAndDataBits.appendBits((byte)(parity & 0x0f), 4);
    
                // Step 4.5: Append ECI message if applicable
                if (mode == Mode.BYTE && !DEFAULT_BYTE_MODE_ENCODING.Equals(encoding))
                {
                    CharacterSetECI eci = CharacterSetECI.getCharacterSetECIByName(encoding);
                    if (eci != null)
                    {
                        appendECI(eci, headerAndDataBits);
                    }
                }
    
                appendModeInfo(mode, headerAndDataBits);
                //BitArray dataBits = new BitArray();
                //int numInputBits = dataBits.Size;
                int numLetters = mode == Mode.BYTE ? dataBits.SizeInBytes : content.Length;
                appendLengthInfo(numLetters, qrCode.Version, mode, headerAndDataBits);
                headerAndDataBits.appendBitArray(dataBits);
    
    
                //  terminateBits(numDataBytes, headerAndDataBits);
                Version version;//=new Version();
                var headerBits = new BitArray();
                if (hints != null && hints.ContainsKey(EncodeHintType.QR_VERSION))
                {
                    int versionNumber = Int32.Parse(hints[EncodeHintType.QR_VERSION].ToString());
                    version = Version.getVersionForNumber(versionNumber);
                    int bitsNeeded = calculateBitsNeeded(mode, headerBits, dataBits, version);
                    if (!willFit(bitsNeeded, version, ecLevel))
                    {
                        throw new WriterException("Data too big for requested version");
                    }
                }
                else
                {
                    version = recommendVersion(ecLevel, mode, headerBits, dataBits);
                }
                var ecBlocks = version.getECBlocksForLevel(ecLevel);
                var numDataBytes = version.TotalCodewords - ecBlocks.TotalECCodewords;
    
                // Terminate the bits properly.
                terminateBits(numDataBytes, headerAndDataBits);
    
                // Interleave data bits with error correction code.
                var finalBits = interleaveWithECBytes(headerAndDataBits,
                                                           version.TotalCodewords,
                                                           numDataBytes,
                                                           ecBlocks.NumBlocks);
    
                 qrCode = new QRCode
                {
                    ECLevel = ecLevel,
                    Mode = mode,
                    Version = version
                };
    
                //  Choose the mask pattern and set to "qrCode".
                var dimension = version.DimensionForVersion;
                var matrix = new ByteMatrix(dimension, dimension);
    
                // Enable manual selection of the pattern to be used via hint
                var maskPattern = -1;
                if (hints != null && hints.ContainsKey(EncodeHintType.QR_MASK_PATTERN))
                {
                    var hintMaskPattern = Int32.Parse(hints[EncodeHintType.QR_MASK_PATTERN].ToString());
                    maskPattern = QRCode.isValidMaskPattern(hintMaskPattern) ? hintMaskPattern : -1;
                }
    
                if (maskPattern == -1)
                {
                    maskPattern = chooseMaskPattern(finalBits, ecLevel, version, matrix);
                }
                qrCode.MaskPattern = maskPattern;
    
                // Build the matrix and set it to "qrCode".
                MatrixUtil.buildMatrix(finalBits, ecLevel, version, maskPattern, matrix);
                qrCode.Matrix = matrix;
    
                //return qrCode;
            }
        }
    
[/code]

これは私は変化しましたZXingで。  
WPFで何をすべきか、どのようにすればよいですか？  
また、JavaコードをC#に変換していますが、間違いがある可能性があります。  
よろしくお願いいたします。




以下のようにすれば出来るでしょう。  
作成した連結QRコードが実際に連結QRコードとして読めるかどうかは確認していませんが、何かしら形の違う4つのQRコードが出来て表示されたのは確認しました。

* * *

ZXing.Netの改造では従来のパラメータは同じまま、連結QRコード用のパラメータを追加して処理を少し変えるだけなので、メソッドのオーバーロードを利用して同じメソッド名で実装出来ます。

`ZXing.Net/Source/lib/qrcode/QRCodeWriter.cs`に連結QRコード用の`encode()`メソッド追加  
元々ある`encode()`メソッドをコピーして、連結QRコード用のパラメータを追加し、連結QRコード用のEncoder.encodeを呼ぶように変更する。

[code]

    //----------------------------------------------------------------------
    /// <summary>
    /// </summary>
    /// <param name="contents">The contents to encode in the barcode</param>
    /// <param name="format">The barcode format to generate</param>
    /// <param name="width">The preferred width in pixels</param>
    /// <param name="height">The preferred height in pixels</param>
    /// <param name="hints">Additional parameters to supply to the encoder</param>
    /// <param name="current">The current number of concatenated QR code</param> // 追加
    /// <param name="last">The last number of concatenated QR code</param>       // 追加
    /// <param name="parity">The parity value of concatenated QR code</param>    // 追加
    /// <returns>
    /// The generated barcode as a Matrix of unsigned bytes (0 == black, 255 == white)
    /// </returns>
    public BitMatrix encode(String contents,
                            BarcodeFormat format,
                            int width,
                            int height,
                            IDictionary<EncodeHintType, object> hints,
                            int current,  // 追加
                            int last,     // 追加
                            byte parity)  // 追加
    
[/code]

途中省略  
以下の行を連結QRコード用のEncoder.encodeを呼ぶように変更  
変更前

[code]

        var code = Encoder.encode(contents, errorCorrectionLevel, hints);
    
[/code]

変更後

[code]

        var code = Encoder.encode(contents, errorCorrectionLevel, hints, current, last, parity);
    
[/code]

以後省略

* * *

`ZXing.Net/Source/lib/qrcode/encoder/Encoder.cs`に連結QRコード用の`encode()`メソッド追加  
元々ある`encode()`メソッドをコピーして、連結QRコード用のパラメータを追加し、連結QRコード用のヘッダを作成するように変更する。

[code]

    //----------------------------------------------------------------------
    /// <summary>
    /// Encodes the specified content.
    /// </summary>
    /// <param name="content">The content.</param>
    /// <param name="ecLevel">The ec level.</param>
    /// <param name="hints">The hints.</param>
    /// <param name="current">The current.</param> // 追加
    /// <param name="last">The last.</param>       // 追加
    /// <param name="parity">The parity.</param>   // 追加
    /// <returns></returns>
    public static QRCode encode(String content,
                       ErrorCorrectionLevel ecLevel,
                       IDictionary<EncodeHintType, object> hints,
                       int current,  // 追加
                       int last,     // 追加
                       byte parity)  // 追加
    
[/code]

途中省略  
以下の行の間で連結QRコード用のヘッダを作成するように追加  
変更前

[code]

        // This will store the header information, like mode and
        // length, as well as "header" segments like an ECI segment.
        var headerBits = new BitArray();
    
        // Append ECI segment if applicable
        if (mode == Mode.BYTE && generateECI)
    
[/code]

変更後

[code]

        // This will store the header information, like mode and
        // length, as well as "header" segments like an ECI segment.
        var headerBits = new BitArray();
    
        //---------------------------------------------------↓ここから追加
        headerBits.appendBits(Mode.STRUCTURED_APPEND.Bits, 4);
        headerBits.appendBits(current, 4);
        headerBits.appendBits(last, 4);
        headerBits.appendBits((byte)((parity & 0xf0) >> 4), 4);
        headerBits.appendBits((byte)(parity & 0x0f), 4);
        //---------------------------------------------------↑ここまで追加
    
        // Append ECI segment if applicable
        if (mode == Mode.BYTE && generateECI)
    
[/code]

以後省略

* * *

アプリケーション側ではこんな感じで使えるでしょう。  
質問の紹介先からパリティ計算処理をコピーする。

[code]

    private byte calcParity(byte[] data)
    {
        byte parity;
    
        parity = data[0];
        for (int i = 1; i < data.Length; i++)
            parity = (byte)(parity ^ data[i]);//XOR
    
        return parity;
    }
    
[/code]

連結QRコード化したい文字列が`ss`に入っているとして、4個のQRコードにしたい場合：

[code]

    string ss = "9MA0GCSqGSIb3DQEBCwUABIIBABVim6AYRyf6AmkZFdAkKvaAvMkQEGYVwa2KUz/FBeu";
    byte parity = calcParity(Encoding.UTF8.GetBytes(ss));                // 全体のパリティ値
    int codesize = Convert.ToInt32(Math.Ceiling((double)ss.Length / 4)); // 個々のQRコードの文字列長
    
    var hints = new Dictionary<EncodeHintType, object> 
    {
        [EncodeHintType.CHARACTER_SET] = "UTF-8",
        [EncodeHintType.ERROR_CORRECTION] = "L",
        [EncodeHintType.MARGIN] = 4,
        [EncodeHintType.QR_VERSION] = 8  // 何か相応しいバージョン(大きさ)を指定しておく
    };
    
    Image[] images = { QrImage1, QrImage2, QrImage3, QrImage4 }; // あらかじめXAMLでImageを4つ作成しておき配列化
    
    QRCodeWriter QrWriter = new QRCodeWriter();  // QRCode作成用
    BarcodeWriter writer = new BarcodeWriter();  // Bitmapへの変換用
    
    for (int i = 0, iOffset = 0; i < 4; i++)
    {
        int length = Math.Min(codesize, ss.Length - iOffset);
        string codestr = ss.Substring(iOffset, length);  // 分割した文字列の切り出し
        iOffset += length;
    
        // 200はQRCodeイメージの適当なサイズ、3は4つの連結QRCodeにする際の最終番号
        using (var bmp = writer.Write(QrWriter.encode(codestr, BarcodeFormat.QR_CODE, 200, 200, hints, i, 3, parity)))
        using (var ms = new MemoryStream())
        {
            bmp.Save(ms, ImageFormat.Bmp);
            // WPFで扱えるImageSourceに変換
            var source = BitmapFrame.Create(ms, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
            images[i].Source = source;
        }
    }
    
[/code]

* * *

ちなみにZXingでは無いけれどこうしたライブラリもあり、こちらでは連結QRcode(structured-
append)がサポートされているようなので、選択肢として考えられるかもしれませんね。  
[libqrencode 日本語](https://fukuchi.org/works/qrencode/index.html.ja) /
[libqrencode english](https://fukuchi.org/works/qrencode/index.html.en)  
[fukuchi/libqrencode](https://github.com/fukuchi/libqrencode)




以下のようにすれば出来るでしょう。  
作成した連結QRコードが実際に連結QRコードとして読めるかどうかは確認していませんが、何かしら形の違う4つのQRコードが出来て表示されたのは確認しました。

* * *

ZXing.Netの改造では従来のパラメータは同じまま、連結QRコード用のパラメータを追加して処理を少し変えるだけなので、メソッドのオーバーロードを利用して同じメソッド名で実装出来ます。

`ZXing.Net/Source/lib/qrcode/QRCodeWriter.cs`に連結QRコード用の`encode()`メソッド追加  
元々ある`encode()`メソッドをコピーして、連結QRコード用のパラメータを追加し、連結QRコード用のEncoder.encodeを呼ぶように変更する。

[code]

    //----------------------------------------------------------------------
    /// <summary>
    /// </summary>
    /// <param name="contents">The contents to encode in the barcode</param>
    /// <param name="format">The barcode format to generate</param>
    /// <param name="width">The preferred width in pixels</param>
    /// <param name="height">The preferred height in pixels</param>
    /// <param name="hints">Additional parameters to supply to the encoder</param>
    /// <param name="current">The current number of concatenated QR code</param> // 追加
    /// <param name="last">The last number of concatenated QR code</param>       // 追加
    /// <param name="parity">The parity value of concatenated QR code</param>    // 追加
    /// <returns>
    /// The generated barcode as a Matrix of unsigned bytes (0 == black, 255 == white)
    /// </returns>
    public BitMatrix encode(String contents,
                            BarcodeFormat format,
                            int width,
                            int height,
                            IDictionary<EncodeHintType, object> hints,
                            int current,  // 追加
                            int last,     // 追加
                            byte parity)  // 追加
    
[/code]

途中省略  
以下の行を連結QRコード用のEncoder.encodeを呼ぶように変更  
変更前

[code]

        var code = Encoder.encode(contents, errorCorrectionLevel, hints);
    
[/code]

変更後

[code]

        var code = Encoder.encode(contents, errorCorrectionLevel, hints, current, last, parity);
    
[/code]

以後省略

* * *

`ZXing.Net/Source/lib/qrcode/encoder/Encoder.cs`に連結QRコード用の`encode()`メソッド追加  
元々ある`encode()`メソッドをコピーして、連結QRコード用のパラメータを追加し、連結QRコード用のヘッダを作成するように変更する。

[code]

    //----------------------------------------------------------------------
    /// <summary>
    /// Encodes the specified content.
    /// </summary>
    /// <param name="content">The content.</param>
    /// <param name="ecLevel">The ec level.</param>
    /// <param name="hints">The hints.</param>
    /// <param name="current">The current.</param> // 追加
    /// <param name="last">The last.</param>       // 追加
    /// <param name="parity">The parity.</param>   // 追加
    /// <returns></returns>
    public static QRCode encode(String content,
                       ErrorCorrectionLevel ecLevel,
                       IDictionary<EncodeHintType, object> hints,
                       int current,  // 追加
                       int last,     // 追加
                       byte parity)  // 追加
    
[/code]

途中省略  
以下の行の間で連結QRコード用のヘッダを作成するように追加  
変更前

[code]

        // This will store the header information, like mode and
        // length, as well as "header" segments like an ECI segment.
        var headerBits = new BitArray();
    
        // Append ECI segment if applicable
        if (mode == Mode.BYTE && generateECI)
    
[/code]

変更後

[code]

        // This will store the header information, like mode and
        // length, as well as "header" segments like an ECI segment.
        var headerBits = new BitArray();
    
        //---------------------------------------------------↓ここから追加
        headerBits.appendBits(Mode.STRUCTURED_APPEND.Bits, 4);
        headerBits.appendBits(current, 4);
        headerBits.appendBits(last, 4);
        headerBits.appendBits((byte)((parity & 0xf0) >> 4), 4);
        headerBits.appendBits((byte)(parity & 0x0f), 4);
        //---------------------------------------------------↑ここまで追加
    
        // Append ECI segment if applicable
        if (mode == Mode.BYTE && generateECI)
    
[/code]

以後省略

* * *

アプリケーション側ではこんな感じで使えるでしょう。  
質問の紹介先からパリティ計算処理をコピーする。

[code]

    private byte calcParity(byte[] data)
    {
        byte parity;
    
        parity = data[0];
        for (int i = 1; i < data.Length; i++)
            parity = (byte)(parity ^ data[i]);//XOR
    
        return parity;
    }
    
[/code]

連結QRコード化したい文字列が`ss`に入っているとして、4個のQRコードにしたい場合：

[code]

    string ss = "9MA0GCSqGSIb3DQEBCwUABIIBABVim6AYRyf6AmkZFdAkKvaAvMkQEGYVwa2KUz/FBeu";
    byte parity = calcParity(Encoding.UTF8.GetBytes(ss));                // 全体のパリティ値
    int codesize = Convert.ToInt32(Math.Ceiling((double)ss.Length / 4)); // 個々のQRコードの文字列長
    
    var hints = new Dictionary<EncodeHintType, object> 
    {
        [EncodeHintType.CHARACTER_SET] = "UTF-8",
        [EncodeHintType.ERROR_CORRECTION] = "L",
        [EncodeHintType.MARGIN] = 4,
        [EncodeHintType.QR_VERSION] = 8  // 何か相応しいバージョン(大きさ)を指定しておく
    };
    
    Image[] images = { QrImage1, QrImage2, QrImage3, QrImage4 }; // あらかじめXAMLでImageを4つ作成しておき配列化
    
    QRCodeWriter QrWriter = new QRCodeWriter();  // QRCode作成用
    BarcodeWriter writer = new BarcodeWriter();  // Bitmapへの変換用
    
    for (int i = 0, iOffset = 0; i < 4; i++)
    {
        int length = Math.Min(codesize, ss.Length - iOffset);
        string codestr = ss.Substring(iOffset, length);  // 分割した文字列の切り出し
        iOffset += length;
    
        // 200はQRCodeイメージの適当なサイズ、3は4つの連結QRCodeにする際の最終番号
        using (var bmp = writer.Write(QrWriter.encode(codestr, BarcodeFormat.QR_CODE, 200, 200, hints, i, 3, parity)))
        using (var ms = new MemoryStream())
        {
            bmp.Save(ms, ImageFormat.Bmp);
            // WPFで扱えるImageSourceに変換
            var source = BitmapFrame.Create(ms, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
            images[i].Source = source;
        }
    }
    
[/code]

* * *

ちなみにZXingでは無いけれどこうしたライブラリもあり、こちらでは連結QRcode(structured-
append)がサポートされているようなので、選択肢として考えられるかもしれませんね。  
[libqrencode 日本語](https://fukuchi.org/works/qrencode/index.html.ja) /
[libqrencode english](https://fukuchi.org/works/qrencode/index.html.en)  
[fukuchi/libqrencode](https://github.com/fukuchi/libqrencode)

