備忘録

備忘録

Laravel MixでWorkboxを利用してオフライン利用可能にする方法

Ⅰ. はじめに

タイトルの通り「Laravel MixでWorkboxを利用してオフライン利用可能にする方法」です。
※この記事では触れませんが manifest.json を追加で作成するとPWA化できます。

Ⅱ. やり方

1. パッケージをインストールする
npm i -D workbox-sw
npm i -D workbox-webpack-plugin
2. ファイルを編集する

welcome.blade.php

...
<script>
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/service-worker.js')
  })
}
</script>
...

webpack.mix.js

const
  mix = require('laravel-mix'),
  workboxPlugin = require('workbox-webpack-plugin');

mix.webpackConfig({
  plugins: [
    new workboxPlugin.GenerateSW({
      swDest: 'service-worker.js',
      clientsClaim: true,
      skipWaiting: true,
      runtimeCaching: [
        {
          urlPattern: new RegExp('/#?/?$'), // 「/」と「/#/」をキャッシュする
          // urlPattern: new RegExp('/'), // 全てキャッシュする
          handler: 'StaleWhileRevalidate',
          options: {
            cacheName: 'index',
            expiration: {
              maxEntries: 1,
              maxAgeSeconds: 24 * 60 * 60 // 24時間
            },
            cacheableResponse: { statuses: [0, 200] }
          }
        }
      ]
    })
  ],
  output: {
    publicPath: ''
  }
});

mix
  .js('resources/js/app.js', 'public/js')
  .sass('resources/sass/app.scss', 'public/css');

実行結果

オフライン状態(①)でもServiceWorkerでキャッシュしている為(②)Webサイトを表示できている。
f:id:kagasu:20190528015326p:plain

Laravel Mixでvue-routerを使う方法

Ⅰ. はじめに

タイトルの通り「Laravel Mixでvue-routerを使う方法」です。

Ⅱ. やり方

1. vue-router をインストールする
npm i -D vue-router
2. ファイルを編集する

resources/views/welcome.blade.php

<!doctype html>
  <head>
    <link rel="stylesheet" href="{{ mix('css/app.css') }}">
  </head>
  <body>
    <div id="app"></div>
    <script src="{{ mix('js/app.js') }}"></script>
  </body>
</html>

/resources/js/app.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'

const app = new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

app.$mount('#app')

/resources/js/router/index.js

import Vue from 'vue'
import Router from 'vue-router'
import Page1 from '../components/Page1'
import Page2 from '../components/Page2'

Vue.use(Router)

export default new Router({
  routes: [
    // { path: '/', name: 'Index', component: Index },
    // { path: '/', redirect: '/page1' },
    { path: '/page1', name: 'Page1', component: Page1 },
    { path: '/page2', name: 'Page2', component: Page2 },
    // { path: '/user/:id', name: 'UserDetail', component: UserDetail, props: true },
  ]
})

/resources/js/App.vue

<template>
  <div>
    <router-link :to="{ name: 'Page1' }">Page1</router-link>
    <router-link :to="{ name: 'Page2' }">Page2</router-link>
    <!-- <router-link :to="{ name: 'UserDetail', params: { id: 1 } }">User1</router-link> -->
    <div id="app">
      <router-view/>
    </div>
  </div>
</template>

/resources/js/components/Page1.vue

<template>
  <h1>Page1.vue</h1>
</template>

/resources/js/components/Page2.vue

<template>
  <h1>Page2.vue</h1>
</template>
3. ビルドする
npm run watch
# npm run prod
4. Webサーバを起動する
php -S localhost:8000 -t public

実行結果

f:id:kagasu:20190527211604p:plain

C#でONNXファイルを利用して手書き数字を認識する方法

Ⅰ. はじめに

タイトルの通り「C#でONNXファイルを利用して手書き数字を認識する方法」です。

Ⅱ. やり方

1. WPFとして新規プロジェクトを作成する

.NET Core / .NET Framework どちらでもOKです。

2. 必要なパッケージをNuGetからインストールする
Install-Package Microsoft.ML.OnnxRuntime
Install-Package SixLabors.ImageSharp -pre
3. MNIST を学習済みの ONNX ファイルをダウンロードし、model.onx をC:\model.onx に保存する

https://github.com/onnx/models/tree/master/mnist

4. サンプルプログラムを書く

MainWindow.xaml

<Window Height="160" Width="210">
  <Grid>
    <InkCanvas x:Name="inkCanvas1" StrokeCollected="InkCanvas1_StrokeCollected" Width="100" Height="100" Margin="10,11,0,0" Background="#FFA0A0A0" HorizontalAlignment="Left" VerticalAlignment="Top" />
    <TextBlock x:Name="textBlock1" FontSize="50" Margin="115,11,0,0" TextWrapping="Wrap" Text="0" VerticalAlignment="Top" HorizontalAlignment="Left" Width="27"/>
    <Button Content="Clear" Click="Button_Click" HorizontalAlignment="Left" Margin="115,83,0,0" VerticalAlignment="Top" Width="73" Height="28"/>
  </Grid>
</Window>

MainWindow.xaml.cs

namespace MNISTTest
{
  public partial class MainWindow : Window
  {
    const int ImgWidth = 28;
    const int ImgHeight = 28;

    public MainWindow()
    {
      InitializeComponent();
    }

    static float[] GetFloatArrayFromImage(byte[] imgBytes)
    {
      var floats = new float[ImgWidth * ImgHeight];

      using (var img = SixLabors.ImageSharp.Image.Load(imgBytes))
      {
        // img.Save(File.Create("out.jpg"), new JpegEncoder() { Quality = 80 });
        img.Mutate(x =>
          x.Resize(ImgWidth, ImgHeight)
          .BinaryThreshold(0.5f)); // 2値化する
        
        for (var x = 0; x < img.Width; x++)
        {
          for (var y = 0; y < img.Height; y++)
          {
            floats[x + y * img.Width] = (img[x, y].R == 255) ? 0 : 1;
          }
        }
      }

      return floats;
    }

    private byte[] GetImageBytes()
    {
      var bounds = VisualTreeHelper.GetDescendantBounds(inkCanvas1);
      var width = (int)inkCanvas1.Width;
      var height = (int)inkCanvas1.Height;
      var rtb = new RenderTargetBitmap(width, height, 96d, 96d, PixelFormats.Pbgra32);
      var drawingVisual = new DrawingVisual();

      using (var ctx = drawingVisual.RenderOpen())
      {
        var vb = new VisualBrush(inkCanvas1);
        ctx.DrawRectangle(vb, null, new Rect(new Point(bounds.X, bounds.Y), new Point(width, height)));
      }

      rtb.Render(drawingVisual);
      var encoder = new BmpBitmapEncoder();
      encoder.Frames.Add(BitmapFrame.Create(rtb));
      using (var ms = new MemoryStream())
      {
        encoder.Save(ms);
        return ms.ToArray();
      }
    }

    private int Check(float[] dimensions)
    {
      using (var session = new InferenceSession("C:\\model.onnx"))
      {
        var inputNodeName = session.InputMetadata.First().Key;
        var innodedims = session.InputMetadata.First().Value.Dimensions;
        var inputTensor = new DenseTensor<float>(dimensions, innodedims);

        var namedOnnxValues = new List<NamedOnnxValue>
        {
          NamedOnnxValue.CreateFromTensor(inputNodeName, inputTensor)
        };

        using (var results = session.Run(namedOnnxValues))
        {
          var resultScores = results.First().AsTensor<float>().ToArray();
          return Array.IndexOf(resultScores, resultScores.Max());
        }
      }
    }

    private void InkCanvas1_StrokeCollected(object sender, InkCanvasStrokeCollectedEventArgs e)
    {
      var imgBytes = GetImageBytes();
      var dimensions = GetFloatArrayFromImage(imgBytes);
      var result = Check(dimensions);

      textBlock1.Text = result.ToString();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
      inkCanvas1.Strokes.Clear();
    }
  }
}

実行結果

f:id:kagasu:20190521161401g:plain

WPFのInkCanvasを画像ファイルとして保存する方法

Ⅰ. はじめに

タイトルの通り「WPFのInkCanvasを画像ファイルとして保存する方法」です。

Ⅱ. やり方

1. サンプルプログラムを書く

MainWindow.xaml

<Window Height="150" Width="150">
  <Grid>
    <InkCanvas
      x:Name="inkCanvas1"
      Width="100"
      Height="100"
      Background="#B2B2B2"
      StrokeCollected="InkCanvas_StrokeCollected" />
  </Grid>
</Window>

MainWindow.xaml.cs

private void SaveImg(UIElement element, string path, BitmapEncoder encoder)
{
  var bounds = VisualTreeHelper.GetDescendantBounds(element);
  var width = (int)bounds.Width;
  var height = (int)bounds.Height;

  var rtb = new RenderTargetBitmap(width, height, 96d, 96d, PixelFormats.Pbgra32);

  var drawingVisual = new DrawingVisual();
  using (var ctx = drawingVisual.RenderOpen())
  {
    var vb = new VisualBrush(element);
    ctx.DrawRectangle(vb, null, new Rect(new Point(bounds.X, bounds.Y), new Point(width, height)));
  }

  rtb.Render(drawingVisual);
  encoder.Frames.Add(BitmapFrame.Create(rtb));
  using (var fs = File.Open(path, FileMode.OpenOrCreate))
  {
    encoder.Save(fs);
  }
}

private void InkCanvas_StrokeCollected(object sender, InkCanvasStrokeCollectedEventArgs e)
{
  SaveImg(inkCanvas1, "out.jpg", new JpegBitmapEncoder());
  SaveImg(inkCanvas1, "out.png", new PngBitmapEncoder());
  SaveImg(inkCanvas1, "out.bmp", new BmpBitmapEncoder());
}

実行結果

f:id:kagasu:20190521150439p:plain

WindowsでC++をWebAssemblyとしてコンパイルして実行する方法

Ⅰ. はじめに

タイトルの通り「WindowsC++をWebAssemblyとしてコンパイルして実行する方法」です。

Ⅱ. やり方

1. Emscripten をインストールする
git clone https://github.com/juj/emsdk.git
cd emsdk

emsdk install latest
emsdk activate latest
2. C++をWebAssemblyとしてコンパイルする

main.cpp

#include <iostream>

int main() {
  std::cout << "hello world" << std::endl;
  return 0;
}
C:\emsdk\emsdk_env.bat
emcc main.cpp -o main.wasm
3. Wasmer をインストールする

WasmerInstaller-x.x.x.exe
https://github.com/wasmerio/wasmer/releases

4. WebAssembly を実行する
wasmer run main.wasm

実行結果

hello world