QRコード生成読取アプリを作ってみた

 QRコードを生成したり読み取るアプリケーションやフリーサービスは、WEB検索したら山ほど結果表示される。なので、あえて自分で作る必要性は感じないと思う。実際あのQRコードを読んだり生成したりするのて難しそうだ。それをC#でアプリケーションを作るのは可能なのかChat-GPTに問いかけてみたら、「そんなの簡単だよ」と短いコーディングを示してくれた。これは私でも作れそうだと思い着手してしまった。というこで最終的には、PCのカメラでQRコードを読み取るというところまで作ってみた。興味がある人は、いろいろアレンジを加えて作ってみてはいかがだろうか。核心部分のコーディングは、ほぼChat-GPT頼みだったけれど。

 必要なものは Microsoft Visual Sutio とそれがインストールできる環境とプログラミング言語C#初級程度の知識。興味があって今から取り組んでみたいというやる気があればいけるかもです。

Quick Response Code Generator Reader

それぞれのパーツの配置や配色はお好みで。表示する画像は最大300×300pixで作成しています。

当アプリケーションができる事
  • Source Data TextBoxに記入された文字列をQRコードにして表示させる。
  • 生成したQRコードが保存可能。
  • Form上にブラウザなどからQRコード画像または文字列をドラッグ&ドロップで読み取ってQRコード生成または読み取った内容をTextBoxに表示する。
  • クリップボードにコピーしたQRコード画像または文字列をCtrl+VでペーストされたらQRコード読み取りまたは文字列をQRコード画像にして表示。
  • カメラが装備されていたらそれを使ってQRコードを読み取る事ができる。

Visual Studio で C# Windows フォームアプリケーション(.NET Framework)で作成開始
※NuGetでインストールしたライブラリーが .NET 8.x 移行に対応してなかった為。

プロジェクトファイル QRCGR.csproj   
  <PropertyGroup> の下に追加
    <LangVersion>14.0</LangVersion> 
※必須ではないが念のため

NuGet で以下のライブラリーをインストール
 AForge ・・・ カメラ用ライブラリー
 QRCode ・・・ QRコード生成用ライブラリー
 ZXing.Net ・・・ バーコードリーダー 用ライブラリー

Formの設定は以下の設定が必須
 AllowDrop = true;
 KeyPreview = true;

Form1.cs ソースコード
using System;
using System.Net;
using System.Net.Http;
using System.Diagnostics;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using ZXing;
using QRCoder;
using AForge.Video;
using AForge.Video.DirectShow;
//
// Quick Respons Code Generator Reader
namespace QRCGR
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            InitializeCamera();
            this.Load += Form1_Load;
        }

        // 起動したPCにカメラが在るか調べる
        private void InitializeCamera()
        {
            // カメラデバイス取得
            videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
            var valid = videoDevices.Count > 0;
            ButtonCamera.Enabled = valid;
            ButtonCamera.Visible = valid;
        }
        //
        //
        private Bitmap qrImage;
        private bool CameraEnbled = false;
        private FilterInfoCollection videoDevices;
        private VideoCaptureDevice videoSource;
        private readonly BarcodeReader reader = new BarcodeReader();
        private bool isDecoding = false;
        readonly Stopwatch frameTimer = new Stopwatch();
        //
        // QRコードを生成してPictureBoxに表示
        private void QRCodeGenerate(string Target, int dot, int pSize)
        {
            var qrGenerator = new QRCodeGenerator();
            QRCodeData qrCodeData =
                qrGenerator.CreateQrCode(Target, QRCodeGenerator.ECCLevel.Q);
            var qrCode = new QRCode(qrCodeData);
            // Bitmap化してPictureBoxに表示
            var qrImg = qrCode.GetGraphic(dot); // 指定ドットサイズで生成
            // 生成された画像が300×300piを超えていたら縮小表示する
            var imgSize = qrImg.Size.Width;
            var size = imgSize > 300 ? pSize : imgSize;
            qrImage = ResizeBitmap(qrImg, size, size);
            PictureBox1.Image = qrImage;
            if (!NudSize.Enabled)
            {
                NudSize.Enabled = true;
                NudDot.Enabled = true;
                ButtonSave.Enabled = true;
            }
            NudSize.ValueChanged -= Object_ValueChanged;
            NudSize.Value = size;
            NudSize.ValueChanged += Object_ValueChanged;
            //
            static Bitmap ResizeBitmap(Bitmap src, int width, int height)
            {
                var result = new Bitmap(width, height);
                using (Graphics g = Graphics.FromImage(result))
                {
                    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
                    g.DrawImage(src, 0, 0, width, height);
                }
                return result;
            }
        }
        //
        // Open internet website
        private static void OpenWebsite(string url)
        {
            var psi = new ProcessStartInfo
            {
                FileName = url,
                UseShellExecute = true
            };
            try
            {
                Process.Start(psi);
            }
            catch (System.ComponentModel.Win32Exception ex)
            {
                MessageBox.Show($"Error: {ex.Message}");
            }
        }
        //
        // 表示内容初期化
        private void Image_Clear()
        {
            qrImage = new Bitmap(100, 100);
            using Graphics g = Graphics.FromImage(qrImage);
            PictureBox1.Image = Properties.Resources.DragDrop; //初期画像
            ButtonExec.Enabled = false;
            ButtonSave.Enabled = false;
            ButtonWeb.Enabled = false;
            NudSize.Enabled = false;
            NudDot.Enabled = false;
        }
        //
        // QRコード画像を読み取った内容をTextBoxに表示する
        private void DecodeQRCodeImage(Bitmap qrimg)
        {
            var result = reader.Decode(qrimg); // QRコードをデコード
            if (result != null)
            {
                TextBox1.Text = result.Text;
            }
            var imgSize = qrimg.Size;
            var sizeW = imgSize.Width;
            var sizeH = imgSize.Height;
            var zw = 300F / (double)sizeW;
            if (zw < 1F)
            {
                sizeW = (int)Math.Round((double)imgSize.Width * zw, 0);
                sizeH = (int)Math.Round((double)imgSize.Height * zw, 0);
            }
            var zh = 300F / (double)sizeH;
            if (zh < 1F)
            {
                sizeW = (int)Math.Round((double)sizeW * zh, 0);
                sizeH = (int)Math.Round((double)sizeH * zh, 0);
            }
            qrImage = new Bitmap(sizeW, sizeH);
            using (Graphics g = Graphics.FromImage(qrImage))
            {
                g.InterpolationMode = InterpolationMode.HighQualityBicubic;
                g.DrawImage(qrimg, 0, 0, sizeW, sizeH);
            }
            PictureBox1.Image = qrImage;
        }
        //
        // textが有効なURLか判定する
        private async Task<bool> UrlText(string text)
        {
            using HttpClient client = new HttpClient();
            try
            {
                HttpResponseMessage response = await client.GetAsync(text);
                return response.IsSuccessStatusCode;
            }
            catch
            {
                return false;
            }
        }
        //
        // カメラ撮影停止
        private void StopCamera()
        {
            frameTimer.Stop();
            isDecoding = false;
            if (videoSource != null && videoSource.IsRunning)
            {
                videoSource.SignalToStop();
                videoSource.WaitForStop();
                videoSource.NewFrame -= VideoSource_NewFrame;
                videoSource = null;
            }
        }
        //
        // [EXEC]ボタンクリック、表示サイズ変更、ドット数変更があったらQRCodeを生成
        private void Object_ValueChanged(object sender, EventArgs e)
        {
            QRCodeGenerate(TextBox1.Text, (int)NudDot.Value, (int)NudSize.Value);
        }
        //
        // [SAVE]ボタンクリック 表示されているQRコード画像を保存する
        private void ButtonSave_Click(object sender, EventArgs e)
        {
            SFD.Filter = "PNGファイル (*.png)|*.png|JPEGファイル (*.jpg)|*.jpg";
            SFD.Title = "QRコードを保存";
            if (SFD.ShowDialog() == DialogResult.OK)
            {
                ImageFormat format = SFD.FilterIndex == 1 
                    ? ImageFormat.Png 
                    : ImageFormat.Jpeg;
                qrImage.Save(SFD.FileName, format);
                MessageBox.Show("保存しました!");
            }
        }
        //
        // [WEB]ボタンクリック -> 有効なURLのWEBを開く
        private void ButtonWeb_Click(object sender, EventArgs e)
        {
            OpenWebsite(TextBox1.Text);
        }
        //
        // [CAMERA]ボタンクリック -> カメラ撮影 on/off
        private void ButtonCamera_Click(object sender, EventArgs e)
        {
            if (CameraEnbled)
            {
                StopCamera();
            }
            else
            {
                TextBox1.Clear();
                // 最初のカメラを使用
                videoSource = new VideoCaptureDevice(videoDevices[0].MonikerString);
                videoSource.NewFrame += VideoSource_NewFrame;
                videoSource.Start();
                frameTimer.Start();
            }
            CameraEnbled ^= true;
        }
        //
        // TextBox1内容が変更されたら有効なURLであればButtonWebを有効にする。
        private async void TextBox1_TextChanged(object sender, EventArgs e)
        {
            var valid = TextBox1.Text.Length > 0;
            ButtonExec.Enabled = valid;
            if (!valid) { Image_Clear(); }
            ButtonWeb.Enabled = await UrlText(TextBox1.Text);
        }
        //
        //
        private void Form1_Load(object sender, EventArgs e)
        {
            PictureBox1.SizeMode = PictureBoxSizeMode.CenterImage;
            Image_Clear();
            TextBox1.TextChanged += TextBox1_TextChanged;
            ButtonExec.Click += Object_ValueChanged;
            ButtonSave.Click += ButtonSave_Click;
            ButtonWeb.Click += ButtonWeb_Click;
            ButtonCamera.Click += ButtonCamera_Click;
            NudSize.ValueChanged += Object_ValueChanged;
            NudDot.ValueChanged += Object_ValueChanged;
            DragDrop += Form1_DragDrop;
            DragEnter += Form1_DragEnter;
            KeyDown += Form1_KeyDown;
            FormClosing += Form1_FormClosing;
        }
        //
        // Form上にドラッグされたデータを判定
        private void Form1_DragEnter(object sender, DragEventArgs e)
        {
            e.Effect = (e.Data.GetDataPresent(DataFormats.FileDrop) ||
                e.Data.GetDataPresent(DataFormats.Bitmap) ||
                e.Data.GetDataPresent(DataFormats.Text))
                ? DragDropEffects.Copy : DragDropEffects.None;
        }
        //
        // Form上にドロップされたデータを読み取る
        private async void Form1_DragDrop(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                string[] dropedFile = (string[])e.Data.GetData(DataFormats.FileDrop);
                foreach (var df in dropedFile)
                {
                    if (Regex.IsMatch(df, ".*[.](png|jpg|jpeg|gif|bmp)$"))
                    {
                        DecodeQRCodeImage(new Bitmap(df));
                        break;
                    }
                }
            }
            if (e.Data.GetDataPresent(DataFormats.Bitmap))
            {
                Bitmap bmp = (Bitmap)e.Data.GetData(DataFormats.Bitmap);
                DecodeQRCodeImage(bmp);
            }
            if (e.Data.GetDataPresent(DataFormats.Text))
            {
                TextBox1.Text = "";
                var text = (string)e.Data.GetData(DataFormats.Text);
                // 画像URLがQRコードだったらDecodeしてみる
                if (Regex.IsMatch(text.ToLower(), ".*[.](png|jpg|jpeg|gif|bmp)$"))
                {
                    if (await UrlText(text))
                    {
                        using var client = new WebClient();
                        byte[] data = await client.DownloadDataTaskAsync(text);
                        using var ms = new System.IO.MemoryStream(data);
                        var img = Image.FromStream(ms);
                        qrImage = new Bitmap(img);
                        DecodeQRCodeImage(qrImage);
                        if (TextBox1.Text.Length > 0) { return; }
                    }
                }
                TextBox1.Text = text;
                ButtonExec.PerformClick();
            }
        }
        //
        // クリップボードからペーストされたデータを受け入れる
        private void Form1_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Control && e.KeyCode == Keys.V)
            {
                IDataObject data = Clipboard.GetDataObject();
                if (data == null) { return; }
                if (data.GetDataPresent(DataFormats.Text))
                {  // Text データだったらTextBox1に表示してQRコード生成
                    TextBox1.Text = (string)data.GetData(DataFormats.Text);
                    ButtonExec.PerformClick();
                }
                if (data.GetDataPresent(DataFormats.Bitmap))
                {  // 画像データだったら読み取る
                    Bitmap bmp = (Bitmap)data.GetData(DataFormats.Bitmap);
                    DecodeQRCodeImage(bmp);
                }
            }
        }
        //
        // Formを閉じる時カメラが撮影中のままだったら閉じる
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (CameraEnbled)
            {
                StopCamera();
            }
        }
        //
        // カメラの画像を読み取る
        private void VideoSource_NewFrame(object sender, NewFrameEventArgs eventArgs)
        {
            try
            {
                Bitmap frame = (Bitmap)eventArgs.Frame.Clone();
                // 🕐 1秒に1回だけPictureBox更新
                if (frameTimer.ElapsedMilliseconds > 1000)
                {
                    frameTimer.Restart(); // 次のタイミングへ
                    this.BeginInvoke((Action)(() =>
                    {
                        PictureBox1.Image?.Dispose();
                        PictureBox1.Image = (Bitmap)frame.Clone();
                    }));
                }
                // QRコード解析が重複しないよう制御
                if (isDecoding) return;
                isDecoding = true;
                // 別スレッドでデコード
                Task.Run(() =>
                {
                    try
                    {
                        var result = reader.Decode(frame);
                        if (result != null)
                        {
                            this.BeginInvoke((Action)(() =>
                            {
                                TextBox1.Text = result.Text;
                                frame.Dispose();
                                ButtonCamera.PerformClick();
                            }));
                        }
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show($"Decode error: {ex.Message}");
                    }
                    finally
                    {
                        frame.Dispose();
                        isDecoding = false;
                    }
                });
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Frame error: {ex.Message}");
            }
        }
    }
}

コメント

  1. C# と C++ は別物と考えた方がいいようです。
    オブジェクトなんちゃらとかさっぱしわかりません。

  2. う~ん、さっぱり分からん。

    Cまではなんとかついて行ってたんですけどねぇ。
    C++のオブジェクト指向ってコトバの意味が理解できず…。

    え?
    C#ってC++とは別なの?