Elsaの技術日記(徒然なるままに)

主に自分で作ったアプリとかの報告・日記を記載

MENU

WPFでスクリーン録画してみる

先日、WPFスクリーンショットやスクリーン画面の動画を表示してみました。
elsammit-beginnerblg.hatenablog.com

今回はこちらのコードに録画機能を追加してスクリーン上の映像を録画してみようと思います!!



■OpenCVSharpを準備する

録画するにあたり、OpenCVSharpを用います。
OpenCVC#版ですね。

OpenCVSharpですが、
Visual Studioを用いている場合には
Nugetパッケージ管理
を用いれば簡単にインストールが可能です。

Nugetパッケージ管理ですが、
ソリューションエクスプローラー上の参照を右クリックすると
こちらの通り、選択項目が出てくるのでこちらをクリック。

Nugetパッケージ管理が表示されたら、
上タブから「参照」を選択し、検索画面で「OpenCVSharp」と入力すると
こちらの通り複数バージョンのOpenCVSharpがリストで出力されます。

今回は、
・OpenCvSharp4
・OpenCvSharp4.runtime.win
をインストールしました。
合わせて、Extensionsも利用したいので、
・OpenCvSharp4.Extensions
もインストールします。

これでOpenCVSharpのインストールは完了です。

WPFでスクリーン録画してみる

では本題のスクリーン録画を行ってみます。
xamlは前回と同様こちらのレイアウトとしました。

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Image x:Name="ImgCap" HorizontalAlignment="Left" Height="343" VerticalAlignment="Top" Width="525" Margin="62,34,0,0"/>
        <Button Content="start" HorizontalAlignment="Left" Height="54" Margin="656,351,0,0" VerticalAlignment="Top" Width="113" Click="Button_Click"/>
    </Grid>
</Window>

録画開始するためのボタンと録画中の映像を表示するImagが1つずつあるのみの構成です。

次にC#のコードですが、こちらの通りになります。

using System;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Drawing;
using System.Windows.Interop;
using System.Threading;
using OpenCvSharp;
using OpenCvSharp.Extensions;

namespace WpfApp1
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : System.Windows.Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Thread thread = new Thread(new ThreadStart(() =>
            {
                CaptureMovieAsync();
            }));
            thread.Start();
        }

        private void CaptureMovieAsync()
        {
            using (var writer = new VideoWriter("test.wmv", FourCC.WMV3, 5, new OpenCvSharp.Size((int)SystemParameters.PrimaryScreenWidth, (int)SystemParameters.PrimaryScreenHeight)))
            {
                for (int i = 0; i < 100; i++)
                {
                    using (var screenBmp = new System.Drawing.Bitmap(
                        (int)SystemParameters.PrimaryScreenWidth,
                        (int)SystemParameters.PrimaryScreenHeight,
                        System.Drawing.Imaging.PixelFormat.Format32bppArgb))
                    {
                        using (var bmpGraphics = Graphics.FromImage(screenBmp))
                        {
                            bmpGraphics.CopyFromScreen(0, 0, 0, 0, screenBmp.Size);

                            Dispatcher.Invoke((Action)(() =>
                            {
                                ImgCap.Source = Imaging.CreateBitmapSourceFromHBitmap(
                                screenBmp.GetHbitmap(),
                                IntPtr.Zero,
                                Int32Rect.Empty,
                                BitmapSizeOptions.FromEmptyOptions());

                                Mat mat = BitmapConverter.ToMat(screenBmp).CvtColor(ColorConversionCodes.RGB2BGR);
                                Cv2.CvtColor(mat, mat, ColorConversionCodes.BGR2RGB);
                                Cv2.Resize(mat, mat, new OpenCvSharp.Size((int)SystemParameters.PrimaryScreenWidth, (int)SystemParameters.PrimaryScreenHeight));
                                writer.Write(mat);
                            }));
                        }
                    }
                    Thread.Sleep(100);
                }
            }    
        }
    }
}

前回のスクリーンショット映像表示のみのコードから追加した点は2点です。
1点目は下記コードになります。

using (var writer = new VideoWriter("test.wmv", FourCC.WMV3, 5, new OpenCvSharp.Size((int)SystemParameters.PrimaryScreenWidth, (int)SystemParameters.PrimaryScreenHeight)))

VideoWriterで動画ファイルへの書き込み用オブジェクトを生成します。
動画ファイルはwmvで出力したかったので、FourCCはWMV3としました。
また、動画サイズは画面サイズと合致するため、
new OpenCvSharp.Size((int)SystemParameters.PrimaryScreenWidth, (int)SystemParameters.PrimaryScreenHeight)
としました。

2点目は下記のコードになります。

Mat mat = BitmapConverter.ToMat(screenBmp).CvtColor(ColorConversionCodes.RGB2BGR);
Cv2.CvtColor(mat, mat, ColorConversionCodes.BGR2RGB);
Cv2.Resize(mat, mat, new OpenCvSharp.Size((int)SystemParameters.PrimaryScreenWidth, (int)SystemParameters.PrimaryScreenHeight));
writer.Write(mat);

こちらは、BitmapをMat型に型変換を行った後、
BGR⇒RGBに色変換とサイズ変更後、1点目で記載したVideoWriterへWriteしています。

本ループですが、、、
100ミリ秒のwaitで0から100までループさせているので、計10秒間実施しています。
要するに10秒間のスクリーン画面の録画を行っている、ということですね。

■最後に

今回はスクリーン画面を録画する方法をまとめてみました。
OpenCVが利用できるので、録画以外にもいろいろなことが出来そうですね。



WPFでスクリーンショットや画面操作動画表示

今回はC#スクリーンショットや画面をリアルタイムで表示する方法をまとめたいと思います。
最終的にはC#で画面録画を実現するまでをまとめたいと思いますが、
今回は画面をWPFのImageに表示するまでをまとめていきます。



■前提条件

今回画面レイアウトですが、こちらの通り
ImageタグとButtonタグのみの構成とします。
用意したボタンをトリガにスクリーンショットや操作映像をImageに表示させてみます。
ボタン押下時に実行される関数名は、「Button_Click」としております。

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Image x:Name="ImgCap" HorizontalAlignment="Left" Height="343" VerticalAlignment="Top" Width="525" Margin="62,34,0,0"/>
        <Button Content="Button" HorizontalAlignment="Left" Height="54" Margin="656,351,0,0" VerticalAlignment="Top" Width="113" Click="Button_Click"/>
    </Grid>
</Window>

スクリーンショット実現方法

まずはWPFで画面のスクリーンショットです。
ボタンを押下した時にスクリーンショットを画面に表示するコードをまとめます。
コードはこちら。

using System;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Drawing;
using System.Windows.Interop;

namespace WpfApp1
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : System.Windows.Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            ImgCap.Source = CopyScreen();
        }
        private static BitmapSource CopyScreen()
        {
            using (var bmtpScreen = new System.Drawing.Bitmap(
                (int)SystemParameters.PrimaryScreenWidth,
                (int)SystemParameters.PrimaryScreenHeight,
                System.Drawing.Imaging.PixelFormat.Format32bppArgb))
            {
                using (var bmpFromImg = Graphics.FromImage(bmtpScreen))
                {
                    bmpFromImg.CopyFromScreen(0, 0, 0, 0, bmtpScreen.Size);
                    
                    return Imaging.CreateBitmapSourceFromHBitmap(
                        bmtpScreen.GetHbitmap(),
                        IntPtr.Zero,
                        Int32Rect.Empty,
                        BitmapSizeOptions.FromEmptyOptions());
                }
            }
        }
    }
}

スクリーンショットを取得している関数ですが、

private static BitmapSource CopyScreen()
{
    using (var bmtpScreen = new System.Drawing.Bitmap(
        (int)SystemParameters.PrimaryScreenWidth,
        (int)SystemParameters.PrimaryScreenHeight,
        System.Drawing.Imaging.PixelFormat.Format32bppArgb))
    {
        using (var bmpFromImg = Graphics.FromImage(bmtpScreen))
        {
            bmpFromImg.CopyFromScreen(0, 0, 0, 0, bmtpScreen.Size);

            return Imaging.CreateBitmapSourceFromHBitmap(
                bmtpScreen.GetHbitmap(),
                IntPtr.Zero,
                Int32Rect.Empty,
                BitmapSizeOptions.FromEmptyOptions());
        }
    }
}

になります。
まず、

bmtpScreen = new System.Drawing.Bitmap

にて新規でDrawing.Bitmap型の変数を生成します。
その後、

var bmpFromImg = Graphics.FromImage(bmtpScreen)

でGraphicsに変換後、

bmpFromImg.CopyFromScreen(0, 0, 0, 0, bmtpScreen.Size);

Windows画面のスクリーンショット結果を取得します。
CopyFromScreen関数ですが、
引数はそれぞれ、
第1引数:転送元の四角形の左上隅の点の x 座標
第2引数:転送元の四角形の左上隅の点の y 座標
第3引数:転送先の四角形の左上隅の点の x 座標
第4引数:転送先の四角形の左上隅の点の y 座標
第5引数:転送される領域のサイズ
になります。
※参考:https://docs.microsoft.com/ja-jp/dotnet/api/system.drawing.graphics.copyfromscreen?view=dotnet-plat-ext-6.0

■動画化してみる

先ほどはボタン押下した際にその瞬間のスクリーンショットを表示するのみでした。
つまり静止画が表示しております。
今度は、ボタン押下後の画面を動画としてリアルタイム表示させる方法をまとめたいと思います。
コードはこちら。

using System;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Drawing;
using System.Windows.Interop;
using System.Threading;

namespace WpfApp1
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : System.Windows.Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {       
            Thread thread = new Thread(new ThreadStart(() =>
            {
                CaptureMovieAsync();
            }));
            thread.Start();
        }

        private void CaptureMovieAsync()
        {
            for (int i = 0; i < 1000; i++)
            {
                using (var screenBmp = new System.Drawing.Bitmap(
                    (int)SystemParameters.PrimaryScreenWidth,
                    (int)SystemParameters.PrimaryScreenHeight,
                    System.Drawing.Imaging.PixelFormat.Format32bppArgb))
                {
                    using (var bmpGraphics = Graphics.FromImage(screenBmp))
                    {
                        bmpGraphics.CopyFromScreen(0, 0, 0, 0, screenBmp.Size);

                        Dispatcher.Invoke((Action)(() =>
                        {
                            ImgCap.Source = Imaging.CreateBitmapSourceFromHBitmap(
                            screenBmp.GetHbitmap(),
                            IntPtr.Zero,
                            Int32Rect.Empty,
                            BitmapSizeOptions.FromEmptyOptions());
                        }));
                    }
                }
                Thread.Sleep(100);
            }
        }
    }
}

今回のコードですが、100秒間画面をリアルタイムで表示させるコードになります。

やっていることは、
ボタン押下時にスレッド生成の上、動作実行し、
スレッド内で先ほどのスクリーンショットを連続で実行している
のみ。です。

スレッド側でオブジェクトの更新を行う必要があるので、
Dispatcher.Invokeを用いております。

■(補足)Dispatcher.Invokeとは?

WPFでは、メインスレッドのみオブジェクトにアクセスでき、別スレッドからオブジェクトにアクセスすることが出来ません。
理由ですが、
メインスレッドで所有するオブジェクトを別スレッドからアクセスできない
ためです。

そこで、別スレッドからアクセスするために
Dispatcher.Invoke
を用います。
このDispatcherはキューを管理するクラスになっており、
メインスレッドに対して、操作したいオブジェクトや処理をキューに格納し、
受け取ったメインスレッドは順次処理を実行しているイメージになります。

■最後に

今回はWPFスクリーンショットや画面をリアルタイム動画で表示する方法でまとめました。
次は表示したスクリーンショットや動画を保存・録画する方法についてまとめたいと思います。



OpenCVで映像のズームイン・ズームアウトを試してみる

今回はOpenCVで映像のズームイン・ズームアウトを行ってみたいと思います。
言語はPythonです。



Pythonバージョン

使用するPythonのバージョンですが、
Python 3.7.4
になります。

■映像のズームイン・ズームアウトのコード

早速、ズームイン・ズームアウトのコードをご紹介します。
コードはこちら。
こちらは、2倍の倍率でズームインしているコードになります。

import cv2

scale = 2 #ここを変更すると倍率が変化する

capture = cv2.VideoCapture(0)

while(True):
    ret, frame = capture.read()

    height, width, channels = frame.shape[:3]
    if int(scale) < 1:
        scale = 1
    roi_width = width / int(scale)
    roi_height = height / int(scale)
    sabun_w1 = int((width - roi_width)/2)
    sabun_w2 = int((width + roi_width)/2)
    sabun_h1 = int((height - roi_height)/2)
    sabun_h2 = int((height + roi_height)/2)
    frame = frame[sabun_w1:sabun_w2, sabun_h1:sabun_h2]
    frame = cv2.resize(frame, (width, height))
    cv2.imshow('frame',frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

capture.release()
cv2.destroyAllWindows()

ズームインの流れをまとめていきます。
一例としてこちらの画像を例に用います。
f:id:Elsammit:20220414222340j:plain

まずは中央の枠の領域を指定するための座標を算出します。
算出しているコードはこちらにあたります。

    roi_width = width / int(scale)
    roi_height = height / int(scale)
    sabun_w1 = int((width - roi_width)/2)
    sabun_w2 = int((width + roi_width)/2)
    sabun_h1 = int((height - roi_height)/2)
    sabun_h2 = int((height + roi_height)/2)

こちらのコードでしている範囲を画像で表すとこちらの範囲になります。
f:id:Elsammit:20220414222714p:plain

次に枠内を切り取ります。
切り取りのコードはこちらにあたります。

frame = frame[sabun_w1:sabun_w2, sabun_h1:sabun_h2]

切り取り後の画像はこちらになります。
f:id:Elsammit:20220414223117p:plain

最後に、切り取り後の画像に対して元の画像サイズにリサイズすることによりズームインが実現出来ます。
該当コードはこちら。

frame = cv2.resize(frame, (width, height))

最終的な画像はこちらの通りとなり、中央領域の画像が拡大します。
f:id:Elsammit:20220414223510p:plain

■動的に拡大率を変更するには
ここまでで、倍率を変更してズームイン・ズームアウトを行う方法をまとめてきました。
次にコンソール上で倍率を入力すると動的にズームイン・ズームアウトを行うコードをまとめていきます。

import cv2
import threading

scale = 1
def input_keybord():
    while True:
        global scale
        scale = input("Enter scale: ")

capture = cv2.VideoCapture(0)

thread1 = threading.Thread(target=input_keybord)
thread1.start()

while(True):
    ret, frame = capture.read()

    height, width, channels = frame.shape[:3]
    if int(scale) < 1:
        scale = 1
    roi_width = width / int(scale)
    roi_height = height / int(scale)
    sabun_w1 = int((width - roi_width)/2)
    sabun_w2 = int((width + roi_width)/2)
    sabun_h1 = int((height - roi_height)/2)
    sabun_h2 = int((height + roi_height)/2)
    frame = frame[sabun_w1:sabun_w2, sabun_h1:sabun_h2]
    frame = cv2.resize(frame, (width, height))
    cv2.imshow('frame',frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

capture.release()
cv2.destroyAllWindows()

先ほど解説したズームインコードとの差分は、

def input_keybord():
    while True:
        global scale
        scale = input("Enter scale: ")

をスレッドで実行してscaleをコンソール上で変更できるようにした点です。
コンソール上で整数値を入力すれば、それに合わせてscaleが変わるので、
倍率が変化してズームイン・ズームアウトが行える原理です。

■実際に動かしてみる
実際にコードを動かして、ズームイン/ズームアウトするか確認していきます。
結果はこちら!!
問題なくズームイン・ズームアウトが実現出来ました。
youtu.be

■最後に

今回はOpenCVでズームイン・ズームアウトする方法をまとめてみました。
OpenCVは様々なことが実現出来、触っていて楽しいです。



React Router v6でページ遷移方法をまとめる

昔、React Routerでページ遷移に関してまとめました。
elsammit-beginnerblg.hatenablog.com

elsammit-beginnerblg.hatenablog.com

どうやらReact Routerがv5⇒v6に上がったことでページ遷移の方法が変わったようです。
今回はv6でのページ遷移についてまとめていきたいと思います。



■条件

今回はページとして、
 ・First
 ・Second
 ・Third
を用意し、
FirstからボタンクリックでSecondとThirdそれぞれにページ遷移させる方法をまとめます。

■React Router v6でのページ遷移方法

では早速、React Router v6でのページ遷移方法をまとめていきます。

まずはルーティングの設定です。
v6ではこちらのようにコードを記載します。

import { BrowserRouter, Route,Routes } from 'react-router-dom'
import Third from './third'
import Second from './second'
import First from './first'

function App() {
  return (
    <div className="App">
      <BrowserRouter>
        <div>
          <Routes>
            <Route path='/third' element={<Third />}/>
            <Route path='/second' element={<Second />}/>
            <Route path='/' element={<First />}/>
          </Routes>
        </div>
      </BrowserRouter>
    </div>
  );
}

export default App;

v6では、

<Routes>
  <Route path='/third' element={<Third />}/>
  <Route path='/second' element={<Second />}/>
  <Route path='/' element={<First />}/>
</Routes>

というようにRoutesを用いる必要があります。

次にfirstからsecondへ遷移させるためのコードをまとめます。
firstのページはこちらのようなコードになります。

import React, { Component } from 'react'
import { Link } from 'react-router-dom';

export default class first extends Component  {
    render () {
        return(
            <div>
                <p>View first page!!</p>
                <button><Link to={"/second"}>Go to Second Page</Link></button>
            </div>
        )
    }
}

そして、secondページはこちらのように実装しました。

import React, { Component } from 'react'

export default class second extends Component  {
    render () {
        return(
            <div>
                <p>View Second page</p>
            </div>
        )
    }
}

ここで重要になるのは、
ページ遷移では、Linkタグを用いるということです。
toで指定したルートへページ遷移させることが出来ます。
今回はsecondに遷移させたいので、

<Link to={"/second"}>Go to Second Page</Link>

としております。

■パラメータを渡しながらページ遷移

では次にパラメータを渡しながらページ遷移をしてみます。
今後は、firstからthirdへパラメータ「hogehoge」を渡してみたいと思います。

firstのコードですが、先ほどのコードの差分のみを掲載します。

    render () {
        return(
            <div>
                <p>View first page!!</p>
                <button><Link to={"/second"}>Go to Second Page</Link></button>
                <button><Link to={"/third"} state={{test:"hogehoge"}}>Go to Third Page</Link></button>
            </div>
        )
    }

追加したコードですが、

<button><Link to={"/third"} state={{test:"hogehoge"}}>Go to Third Page</Link></button>

となります。
パラメータを渡す場合には、

state={{test:"hogehoge"}}

といったjson形式でstateへ定義していけばよいです。

次にthridページを実装していきます。
コードはこちら。

import React, { Component } from 'react'
import { useLocation } from 'react-router-dom';

class Third extends Component{
    render(){
        return <p>View Third page</p>
    }

}

export default () => {
    const location = useLocation();
    const { st} = location.state;
    return (<div>
        <Third></Third>
        {st}
        </div>
    );
};

パラメータを受け取る際、useLocationを用います。
ただし、useLocationはclassでは用いることが出来ないようなので、

export default () => {
    const location = useLocation();
    const { test} = location.state;
・・・

というように、useLocationを用いてかつ location.stateから
firstでパラメータとして渡されたtest:"hogehoge"を受け取ります。
これにより、変数testに"hogehoge"が代入されました。

合わせて今回は、

class Third extends Component{
    render(){
        return <p>View Third page</p>
    }
}

でclassを別途定義してみました。
今回は簡単にView Third pageを表示するのみになります。

■実際に動かしてみる。

では、こちらのコードを実際に動かしてみたいと思います!!

まずは、first⇒secondへのページ遷移です。
firstからsecondへページ遷移出来ているのが確認できました。
f:id:Elsammit:20220410225136g:plain

次にfirst⇒thirdへのページ遷移です。
firstからthirdへページ遷移出来ておりかつ、変数として与えたhogehogeが表示されているのが確認できました。
f:id:Elsammit:20220410225159g:plain

■最後に

今回はReact Router v6でのページ遷移方法に関してまとめてみました。
まだまだ不慣れな点があるのでもう少し勉強してみます!!
学んだことはブログにまとめていきたいな。



flaskでREST APIを体験してみる

今更感ありますが、、、今回はREST APIについてまとめていきたいと思います。
また、REST APIについて記事や本を読んでもよく分からず、
実際にREST APIを体験するためにflaskでREST APIを作成してみましたので、
備忘録も兼ねてブログにまとめたいと思います!!



REST APIとは?

そもそもRESTとは、、
REpresentational State Transferの略になります。

このRESTに関して、RESTの4原則があり、この4原則を満たすものをRESTfulと呼んだりします。
RESTの4原則ですが、

1.セッションなどの状態管理を行わない。(やり取りされる情報はそれ自体で完結して解釈することができる)
2.情報を操作する命令の体系が予め定義・共有されている。(HTTPのGETやPOSTメソッドなど)
3.すべての情報は汎用的な構文で一意に識別される。(URLやURIなど)
4.情報の内部に、別の情報や(その情報の別の)状態へのリンクを含めることができる。

になります。

このRESTの考えを用いてAPIを設計・作成したものをREST APIと言います。

具体的には、
RESTful APIとは、Webシステムを外部から利用するためのプログラムの呼び出し規約(API)の種類の一つで、
「REST」(レスト)と呼ばれる設計原則に従って策定されたもの。
RESTそのものは適用範囲の広い抽象的なモデルだが、
一般的にはRESTの考え方をWeb APIに適用したものをRESTful APIと呼んでいる。
※参考:RESTful API(REST API)とは - IT用語辞典 e-Words

とのことです!!

具体的にはこちらの記事をご参照ください。
0からREST APIについて調べてみた - Qiita

こちらに掲載されている、
RESTなAPIとそうではないAPIの例
が個人的に分かりやすく、REST APIに関して理解が進みました。

■FlaskでREST APIを作成してみる

REST APIに関して文字でまとめてみましたが、、
文字だけではよく分からない!!と思いましたので、実際に作成して動かすことで体験してみました。

今回使用するのはpythonのFlaskです。
Flaskを使用した理由ですが、
個人的に一番使い慣れたサーバーサイドのWebアプリケーションフレームワークだったためです。

では早速コードを載せます。
今回作成したコードはこちら!!

from flask import Flask, jsonify, abort, request
import json

app = Flask(__name__)

datas = [
    {"id":1, "name":"hoge", "category":"manga","quantity":5},
    {"id":2, "name":"huga", "category":"magazine","quantity":10},
    {"id":3, "name":"hogehuga", "category":"magazine","quantity":1}
]

# 一覧取得
@app.route('/bookstore', methods=['GET'])
def findBookFromName():
    retList = []
    
    for d in datas:
        retList.append(d)

    if len(retList) > 0:
        return jsonify(retList)
    else:
        return "",400

# idで指定した項目の抽出
@app.route('/bookstore/<int:id>', methods=['GET'])
def findBookFromId(id):
    ret = ""
    for d in datas:
        if d["id"] == id:
            ret = d
    
    if ret != "":
        return jsonify(ret)
    else:
        return "",400

# データ追加
@app.route('/bookstore', methods=['POST'])
def addBooks():
    postedData = request.data.decode('utf-8')
    postedData = json.loads(postedData)
    bookData = {
        "id":int(postedData["id"]),
        "name":postedData["name"],
        "category":postedData["category"],
        "quantity":int(postedData["quantity"])
    }
    datas.append(bookData)

    return '',200

#データ更新
@app.route('/bookstore/', methods=['PUT'])
def putBook():
    putData = request.data.decode('utf-8')
    putData = json.loads(putData)
    ret = ""
    print(putData["name"])
    for d in datas:
        if d["id"] == putData["id"]:
            ret = d

    ret["name"] = putData["name"]
    ret["category"] = putData["category"]
    ret["quantity"] = putData["quantity"]

    return '',200


if __name__ == "__main__":
    app.run(debug=True)

APIはそれぞれこちらのようになります。
・一覧取得        :('/bookstore', methods=['GET'])
・idで検索したデータを抽出 :app.route('/bookstore/', methods=['GET'])
・データ追加       :app.route('/bookstore', methods=['POST'])
・データ更新       :app.route('/bookstore/', methods=['PUT'])

では動作確認をしていきます。
curlコマンドを用いてレスポンスを確認する最も簡単になります。

先ほどのコードを実行した上でcurlコマンドを用いて、

curl GET http://127.0.0.1:5000/bookstore

と実行してみてください。

すると、

[
  {
    "category": "manga",
    "id": 1,
    "name": "hoge",
    "quantity": 5
  },
  {
    "category": "magazine",
    "id": 2,
    "name": "huga",
    "quantity": 10
  },
  {
    "category": "magazine",
    "id": 3,
    "name": "hogehuga",
    "quantity": 1
  }
]

といったjson形式であらかじめ登録していたdata変数が表示されるかと思います。

datas = [
    {"id":1, "name":"hoge", "category":"manga","quantity":5},
    {"id":2, "name":"huga", "category":"magazine","quantity":10},
    {"id":3, "name":"hogehuga", "category":"magazine","quantity":1}
]

■最後に

今回はREST APIに関してまとめてみました。
基本的な部分は理解できたのですが、実際にREST APIで複雑なWebアプリを作成する場合にはどうなるんだろう??
逆に複雑化したりしないのかな?
今度Webアプリ作成する際には意識してみようかな?



C++でラムダ式を用いたコールバック関数作成

今回はラムダ関数を用いてC++でコールバック関数を作成していきたいと思います!!
クラス間でコールバック関数を用いてメンバ変数に値をSet/Getしてみたかったので調べてみたところ、
便利そうな方法を見つけたので備忘録としてまとめることに。



ラムダ式とは?

ラムダ式とは、
簡易的な関数オブジェクトをその場で定義するための機能になります。

例えば、

#include <stdio.h>

int main(){
        auto add = [](int a,int b){
                return a + b;
        };
        int result = add(3,5);
        printf("%d \n", result);

        return 0;
}

をビルド⇒実行すると、
resultには3+5の結果である8が格納されます。
ラムダ式は、

auto add = [](int a,int b){
       return a + b;
};

というような形式で書かれます。
意味合いとしては、
「int型のパラメータを2つとり、それらを足し合わせたint型オブジェクトを返す関数オブジェクト」を定義している。
となります。

さらに、ラムダ式の外側の変数を使用する場合には、「キャプチャ」という機能を利用します。
[ ]にどの変数をどのようにキャプチャするかを示します。
例えば、

#include <stdio.h>

int main(){
        int x = 3;
        auto chgval = [&]{x = 1;};
        chgval();

        auto addfive = [=]{ return x + 5;};
        
        int result = addfive();
        printf("%d \n", result);

        return 0;
}

といったコードをビルド⇒実行するとresultに6が格納されます。

これは、

auto chgval = [&]{x = 1;};

ラムダ式の外部で定義したxを1に置き換え、

auto addfive = [=]{ return x + 5;};

を実行するとxに格納された1と5が足し合わされた6が返る式になります。

ラムダ式を用いたコールバック関数

では早速ラムダ式を用いてコールバック関数を作成していきます。
今回はClassAとClassBを作成し、ClassBからClassAへコールバック関数を用いて値をセットしに行くコードを作成します。
まず各コードですがClassA、ClassBそれぞれこちらのようになります。

ClassA.hpp

#include <stdio.h>
#include "ClassB.hpp"

class hogeA{
private:
    int NumA;
    int NumB;
    hogeB m_hogeB = hogeB();
public:
    hogeA(){
        NumA = 0;
        NumB = 0;
    }
    void DoingFunc();
    void SetNumber(int a,int b);
    int GetNumberA(){return NumA;}
    int GetNumberB(){return NumB;}
};

ClassA.cpp

void hogeA::DoingFunc(){
    m_hogeB.Set_HogeA([](int a,int b){
        SetNumber(a,b);
    });
}

void hogeA::SetNumber(int a,int b){
    NumA = a;
    NumB = b;
}

ClassB.hpp

#include <stdio.h>
#include <functional>

class hogeB{
    typedef std::function<void(int,int)> FUNC_POINTER;
    FUNC_POINTER p;
    
public:
    void Set_HogeA(const FUNC_POINTER &func);
    void Set_func();
};

classB.cpp

#include <stdio.h>
#include "ClassB.hpp"

void hogeB::Set_HogeA(const FUNC_POINTER &func){
    p = func;
}

void hogeB::Set_func(){
    p(1,2);
}

まずはClassBに関してですが、

void hogeB::Set_HogeA(const FUNC_POINTER &func){
    p = func;
}

でコールバックをセットする関数を用意します。
FUNC_POINTER ですが、

typedef std::function<void(int,int)> FUNC_POINTER;
FUNC_POINTER p;

と定義しております。

次にClassAですが、
先ほどのラムダ式を用いて、

void hogeA::DoingFunc(){
    m_hogeB.Set_HogeA([&](int a,int b){
        SetNumber(a,b);
    });
}

というようにClassB側にコールバック関数として
SetNumber(a,b);
をセットします。

コールバック関数を使っても旨味の無い処理になってしまいますが、、、
試しにこちらのコードを作成し実行してみます。

int main(){
    hogeA m_hogeA = hogeA();
    hogeB m_hogeB = hogeB();
    
    printf("hogeA:%d \n", m_hogeA.GetNumberA());
    printf("hogeB:%d \n", m_hogeA.GetNumberB());
    printf("============================ \n");
    m_hogeA.DoingFunc();
    m_hogeB.Set_func();

    printf("hogeA:%d \n", m_hogeA.GetNumberA());
    printf("hogeB:%d \n", m_hogeA.GetNumberB());
    printf("============================ \n");
}

結果は下記の通りになるかと思います。

hogeA:0
hogeB:0
============================
hogeA:1
hogeB:2
============================

これは、

m_hogeA.DoingFunc();

でClassBにClassAで定義したSet関数をコールバック関数として定義し、

m_hogeB.Set_func();

によりコールバック関数を用いてClassBからClassAのメンバ変数にSet関数を用いて値(1,2)をセットしたことによります。

■最後に

今回はラムダ式を用いてコールバック関数を作成してみました。
ラムダ式を用いると結構簡単にコールバック関数作成できて便利。



github APIを用いたコマンドでのIssueの取得や登録

今回はgithub上に上がっているIssueの一覧を取得する方法についてまとめていきたいと思います。

個人やOSSなど、ソースコードやバグ・要望などなどをgithub上で管理することがあるかと思います。
その際に、バグや要望の一覧が見たいな!!となったことはないでしょうか?
今回は、APIを用いてIssueの一覧を取得してみたいと思います。



APIを用いてgithub上のリポジトリからIssueを取得

githubではCUIで操作するためのAPIが用意されております。
下記が公式のドキュメントになります。
REST APIを使ってみる - GitHub Docs

こちらのAPIを使用するためには、
アクセストーク
が必要になります。

アクセストークンの取得方法はこちらの公式ページに詳しくまとまっておりますが、
Settings ⇒ Developer settings
に移動して、
Personal access tokens
でアクセストークンを作成すればOKです。
docs.github.com


後はcurlコマンドと先ほどのAPIを用いて、、

curl -H "Authorization: token $TOKEN" https://api.github.com/repos/$USER_NAME/$REPO_NAME/issues

と実行すれば、指定したリポジトリに対するIssueの一覧をjson形式で取得が可能です。
ここで、、
 ・$TOKEN:アクセストーク
 ・$USER_NAME:ユーザ名
 ・$REPO_NAME:リポジトリ
を指定ください。

もしOpenされたIssueのみを取得したい場合には、

curl -H "Authorization: token $TOKEN" https://api.github.com/repos/$USER_NAME/$REPO_NAME/issues?state=open

とすればOKです。

■Issueの登録

github上のリポジトリにIssueを登録する場合にはこちらのスクリプトを実行すればOKです。

curl -H "Authorization: token $TOKEN" \
    -X POST \
    -d'{"title": "test Issue", "body":"testです", "labels":["bug"]}' \
    https://api.github.com/repos/$USER_NAME/$REPO_NAME/issues

ここで、
 ・$TOKEN:アクセストーク
 ・$USER_NAME:ユーザ名
 ・$REPO_NAME:リポジトリ
となるのは先ほどのIssue取得と同様です。

■(おまけ)Issueへのコメント追加

github上のIssueに対してコメントを追加する場合には、

curl -H "Authorization: token $TOKEN" \
    -X POST \
    -d'{"body":"コメントテスト"}' \
    https://api.github.com/repos/$USER_NAME/$REPO_NAME/issues/$IssueNumber/comments

とすればOK。
$IssueNumberには登録されているIssueの番号(#**)が入ります。

■最後に

今回はAPIを用いてgithub上のIssueの一覧取得や登録、そしてコメント追加までを行ってみました。
少しでもお役に立てれば幸いです!!