asp.net coreでサービス作ったから宣伝兼ねて技術公開する -2_6.分割画面（Canvasオブジェクト）-

##設計方針について
さて、どのように棚から本にするか。
アーキテクトの記事でざっくり記載しておりましたが、こんな感じを想定しておりました。

↑理想↑
1.AIがゴニョゴニョして、うまく分割！
2.OpenCVでゴニョゴニョして、うまく分割！
3.OCRで取得できた文字位置から、うまく分割！
4.手動で分割！
↓現実↓

実装したのは、当然 4。
今後、3に寄せていって、徐々に 1 へ近づけていくと。。。
何年かかるんだか。

ということで、ブラウザのCanvasを使った記事となります。


##棚画像を分割する方法について(TanaToru)

要件は以下の感じ
・マウス操作だけ
・できるだけまとめてやりたい
・連続で分割したいから、非同期でやりたい

↓

で、出来たのがこんな感じのです。

↓

1.棚の上下の座標を設定
2.本の文字部分をマウスで選択
3.テンキーで本の数を入力[1-10]まで
-ここから非同期-
4.棚写真から本を分割
5.それぞれをOCR処理
6.取得した文字を本検索
7.検索で一番近い1件を登録
-ここまで非同期-
8.画面に結果表示（OK 1件、NG 1件 みたいな感じで返す）

##棚画像を分割する方法について(Qiita説明用)

Qiitaでこれを全部説明したところでニーズもないので、掻い摘んでの説明になります！
1.本の文字部分をマウスで選択
2.Enterで本の画像を送信
-ここから非同期-
3.画像を分割
4.OCR処理
5.取得した文字を本検索
-ここまで非同期-
6.画面に結果表示



それでは、Canvasの使い方から。

##画面作成

前回のPGが残ってしまっているので、少しデバッグしやすいようにします。

・Workメニューをログインせずに表示
・Workにリンクを作成し、新たにCanvasを作る。

それでは共通メニューから修正
ログインされていれば表示する部分をコメントアウトしておきます。

```Views\Shared\_Layout.cshtml
@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - sample2_1</title>

    <environment include="Development">
        <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    </environment>
    <environment exclude="Development">
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css"
              asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
              asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute"
              crossorigin="anonymous"
              integrity="sha256-eSi1q2PG6J7g7ib17yAaWMcrr5GrtohYChqibrV7PBE=" />
    </environment>
    <link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">sample2_1</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <partial name="_LoginPartial" />
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
                        </li>
                        @*Workメニューの追加 ここから*@
                        @*@if (SignInManager.IsSignedIn(User))
                        {*@
                            <li class="nav-item">
                                <a class="nav-link text-dark" asp-area="" asp-controller="Work" asp-action="Index">Work</a>
                            </li>
                        @*}*@
                        @*Workメニューの追加 ここまで*@
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <partial name="_CookieConsentPartial" />
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2019 - sample2_1 - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
        </div>
    </footer>

    <environment include="Development">
        <script src="~/lib/jquery/dist/jquery.js"></script>
        <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
    </environment>
    <environment exclude="Development">
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"
                asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
                asp-fallback-test="window.jQuery"
                crossorigin="anonymous"
                integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=">
        </script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/js/bootstrap.bundle.min.js"
                asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"
                asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
                crossorigin="anonymous"
                integrity="sha256-E/V4cWE4qvAeO5MOhjtGtqDzPndRO1LBk8lJ/PR7CA4=">
        </script>
    </environment>
    <script src="~/js/site.js" asp-append-version="true"></script>

    @RenderSection("Scripts", required: false)
</body>
</html>
```

次に、Controllerにメソッド追加
余計なコードはありますが、気にしない。気にしない。
よくサンプルコードあるけど、中途半端に記載するやつ、あんまり好きじゃないんです。
たまにusingが記載してなくて困ることがあったりするので。


```Controllers\WorkController.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Amazon;
using Amazon.S3;
using Amazon.S3.Transfer;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using sample2_1.Models;
using sample2_1.Models.Const;

namespace sample2_1.Controllers
{
    public class WorkController : Controller
    {
        // AmazonS3 インターフェイス
        private static IAmazonS3 client;

        public IActionResult Index()
        {
            /* ログインされている場合、共通受け渡しデータにユーザ名を入れておく */
            if (User.Identity.IsAuthenticated)
            {
                var user_id = "";
                var user_nm = "";
                // ユーザIDの特定
                foreach (var i in this.User.Identities)
                {
                    foreach (var n in i.Claims)
                    {
                        if (n.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")
                        {
                            user_id = n.Value;
                        }
                        if (user_id != "") break;
                    }
                    if (user_id != "") break;
                }

                // ユーザマスタから情報取得
                using (var db = new AppDbContext())
                {
                    // [出力]
                    foreach (var mUser in db.MUsers.Where(m => m.UserId == user_id))
                    {
                        user_nm = mUser.UserNm;
                        break;
                    }
                }

                ViewData["user_nm"] = user_nm;
            }



            return View();
        }

        [HttpPost("UploadFiles")]
        public async Task<IActionResult> FileUpload(List<IFormFile> files)
        {
            // 保存先を取得
            string filePath = @"C:\work\";

            // Amazon S3 キーを取得(準備で取得したキーを設定してください。)
            // このキーがバレると、勝手に使われてしまう可能性があるので、お気をつけて
            var accessKey = ConstKey.AWS_KEY;
            var accessSecretKey = ConstKey.AWS_SECRET_KEY;

            // ファイル保存
            long size = files.Sum(f => f.Length);
            foreach (var formFile in files)
            {
                if (formFile.Length <= 0) continue;
                // ローカルに保存
                using (var stream = new FileStream(filePath + formFile.FileName, FileMode.Create))
                {
                    await formFile.CopyToAsync(stream);
                }

                // Amazon S3 クライアントの設定
                client = new AmazonS3Client(accessKey
                                           , accessSecretKey
                                           , RegionEndpoint.USEast2);
                // 非同期メソッドの実行
                WritingAnObjectAsync(formFile.FileName).Wait();


            }

            // 画面に返す返却値
            ViewData["uploadResult"] = Ok(new { count = files.Count, size, filePath }).Value.ToString();

            // ViewをIndex画面で返却
            return View("Index");
        }

        // S3 ストレージへアップロード！
        static async Task WritingAnObjectAsync(string s_upd_file_nm)
        {

            // S3の保存先を設定
            string S3BucketName = @"tanatoru-s3";
            string S3Key = @"work/";

            // 元のファイル名をそのまま利用
            S3Key += s_upd_file_nm;

            try
            {
                // キーを設定したclientを引数に、アップロードクラスを取得
                var fileTransferUtility = new TransferUtility(client);

                // アップロード処理
                using (var fileToUpload =
                    new FileStream(@"c:\work\" + s_upd_file_nm, FileMode.Open, FileAccess.Read))
                {
                    await fileTransferUtility.UploadAsync(fileToUpload
                                                        ,S3BucketName
                                                        ,S3Key);
                }
                // OK！
                Console.WriteLine("Upload 3 completed");

            }
            catch (AmazonS3Exception e)
            {
                Console.WriteLine(
                        "Error encountered ***. Message:'{0}' when writing an object"
                        , e.Message);
            }
            catch (Exception e)
            {
                Console.WriteLine(
                    "Unknown encountered on server. Message:'{0}' when writing an object"
                    , e.Message);
            }
        }

        //asp.net coreでサービス作ったから宣伝兼ねて技術公開する -2_6.分割画面→Canvasオブジェクト- 追加
        public IActionResult Canvas()
        {
            return View();
        }
    }
}
```

次にビューを新規作成

```Views\Work\Canvas.cshtml
@{
    ViewData["Title"] = "Canvas Page";
}
```

最後に、Indexにリンクボタンを設置！

```Views\Work\Index.cshtml
@{
    ViewData["Title"] = "Work Page";
}

<div class="text-center">
    <h1 class="display-4">Working Page</h1>
</div>

<hr>

@Html.ActionLink("Canvas Page","Canvas")

<hr>
以下略
```

一旦確認、実行！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/249830/f084e843-1fb3-e98a-0b67-73feada21e91.png)

リンククリック！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/249830/f1f67ac0-490a-8edb-82dd-4e951760159a.png)

OK！
それでは、ここから埋め込んでいきましょう。


##Canvasの実装

Canvasはブラウザ上で画像を動的に操作することが出来ます。

そして、以下のサイトなしに実装は出来ませんでした。
ありがとう！

画像をマウスで範囲選択する[Canvasの矩形選択1]：
　https://www.petitmonte.com/javascript/canvas_select_range.html


ってことで、作ったのはこんな感じです。


```Views\Work\Index.cshtml
@{
    ViewData["Title"] = "Canvas Page";
}

<style type="text/css">
    canvas {
        border: solid 3px #000;
    }
</style>

<canvas id="SrcCanvas"
        style="height:90%;width:100%;"></canvas>

<script src="~/js/canvas.js"></script>
<script type="text/javascript">
    canvas_init("/img/work.img"));
</script>
```


```wwwroot\js\canvas.js
// キャンバス
var src_canvas;
var src_ctx;

// 矩形用
var rect_MousedownFlg = false;
var rect_fx = 0;
var rect_fy = 0;
var rect_ex = 0;
var rect_ey = 0;

// イメージ
var img = new Image();

// 座標補正係数
var x_keisu = 1.0;
var y_keisu = 1.0;

// 棚SEQ
var tana_seq = 0;

// 初期処理
function canvas_init(img_src) {
    src_canvas = document.getElementById("SrcCanvas");
    src_ctx = src_canvas.getContext("2d");

    /* Imageオブジェクトを生成 */
    img.src = img_src;

    /* 画像が読み込まれるのを待ってから処理を続行 */
    img.onload = function () {

        src_canvas.width = img.width;
        src_canvas.height = img.height;

        src_ctx.drawImage(img, 0, 0);
    }

    // イベントを追加
    src_canvas.setAttribute('tabindex', 0);
    src_canvas.addEventListener('mousedown', mousedown, false);
    src_canvas.addEventListener('mousemove', mousemove, false);
    src_canvas.addEventListener('mouseup', mouseup, false);
    src_canvas.addEventListener('keydown', keydown, false);
}

// マウスクリック時
function mousedown(event) {

    rect_MousedownFlg = true;
    src_canvas = document.getElementById("SrcCanvas");
    var rect = event.target.getBoundingClientRect();

    // 係数を算出
    x_keisu = src_canvas.width / rect.width;
    y_keisu = src_canvas.height / rect.height;


    // 座標を求める
    rect_fx = (event.clientX - src_canvas.offsetLeft) * x_keisu;
    rect_fy = (event.clientY - src_canvas.offsetTop) * y_keisu;

    // 矩形の枠色を反転させる  
    var imagedata = src_ctx.getImageData(rect_fx, rect_fy, 1, 1);
    src_ctx.strokeStyle = 'rgb(' + getTurningAround(imagedata.data[0]) +
        ',' + getTurningAround(imagedata.data[1]) +
        ',' + getTurningAround(imagedata.data[2]) + ')';
    // 線の太さ                         
    src_ctx.lineWidth = 6;

    // 矩形の枠線を点線にする
    src_ctx.setLineDash([2, 3]);

}

// マウス移動時
function mousemove(event) {
    if (rect_MousedownFlg) {

        // 座標を求める
        var rect = event.target.getBoundingClientRect();
        rect_ex = (event.clientX - src_canvas.offsetLeft) * x_keisu;
        rect_ey = (event.clientY - src_canvas.offsetTop) * y_keisu;

        // 元画像の再描画
        src_ctx.drawImage(img, 0, 0);

        // 矩形の描画
        src_ctx.beginPath();

        // 上
        src_ctx.moveTo(rect_fx, rect_fy);
        src_ctx.lineTo(rect_ex, rect_fy);

        // 下
        src_ctx.moveTo(rect_fx, rect_ey);
        src_ctx.lineTo(rect_ex, rect_ey);

        // 右
        src_ctx.moveTo(rect_ex, rect_fy);
        src_ctx.lineTo(rect_ex, rect_ey);

        // 左
        src_ctx.moveTo(rect_fx, rect_fy);
        src_ctx.lineTo(rect_fx, rect_ey);

        src_ctx.stroke();
    }
}

// マウス移動時
function mouseup(event) {
    rect_MousedownFlg = false;

    // 座標を求める
    var rect = event.target.getBoundingClientRect();
    rect_ex = (event.clientX - src_canvas.offsetLeft) * x_keisu;
    rect_ey = (event.clientY - src_canvas.offsetTop) * y_keisu;


    // 青
    src_ctx.strokeStyle = 'rgb(255,255,0)';
    // 線の太さ                         
    src_ctx.lineWidth = 10;
    // 矩形の描画
    src_ctx.beginPath();
    // 上
    src_ctx.moveTo(0, 0);
    src_ctx.lineTo(img.width, 0);
    src_ctx.stroke();
}

// 色の反転
function getTurningAround(color) {
    // 灰色は白にする 
    if (color >= 88 && color <= 168) {
        return 255;
        // 色を反転する  
    } else {
        return 255 - color;
    }
}
```

これで、画像をマウスで範囲指定出来るようになります！
画像の座標を把握することが出来ます。
画像サイズと表示サイズが異なっているので、係数を求めて算出させてます。

こんな感じで、マウスのドラッグした範囲が点線で表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/249830/44981d4a-a0c2-f87b-3e5c-573cdf4193da.png)




##画像の分割処理するための、前準備
それでは次に、指定した座標を切り取った画像にしましょう。

それにはまず、情報を送る必要がありますね。

jsにKeyDownイベントを追加し、Enterキーだった場合に、jsからControllerへPostする仕組みを作成します。
参考サイトを紹介しようとしたのですが、様々なサイトを参考にして実装したので、
どれがどれだか分からなかった。

ついでに画面上への結果表示用にメッセージをポップアップさせるイベントも追加します。

参考サイト：
[JavaScript] WEBブラウザでスプラッシュメッセージを表示する
　https://qiita.com/RAWSEQ/items/fec6cef0cab3e50fa07d


```Views\Work\Index.cshtml
// ※ 上の続きです。最後に全部載っけます。

// 範囲指定後
function keydown(event) {

    if (event.keyCode != 13) {
        return;
    }

    var v_x;
    var v_y;
    var v_w;
    var v_h;
    if (rect_ex - rect_fx > 0) {
        v_x = rect_fx;
        v_w = rect_ex - rect_fx;
    } else {
        v_x = rect_ex;
        v_w = rect_fx - rect_ex;
    }

    if (rect_ey - rect_fy > 0) {
        v_y = rect_fy;
        v_h = rect_ey - rect_fy;
    } else {
        v_y = rect_ey;
        v_h = rect_fy - rect_ey;
    }

    // うまく取れない場合、エラーとする。
    if (v_w <= 0 || v_h <= 0) {
        return;
    }

    var j = {
        img_path: img.src
        , x: v_x
        , y: v_y
        , width: v_w
        , height: v_h
    };

    var jsonString = JSON.stringify(j);

    $.ajax({
        type: "POST", // 省略可（省略時は"GET"）
        url: "/Work/Cut",
        traditional: true,
        contentType: "application/json; charset=utf-8",
        data: jsonString,
        cache: false, // 省略可（省略時はtrue）
        timeout: 120000 // 省略可
    })
        // 成功時
        .done(function (data, textStatus, jqXHR) {
            splash(data);
        })
        // 失敗時
        .fail(function (jqXHR, textStatus, errorThrown) {
            splash("正常に処理が行なえませんでした。");
        });
}


splash = function (msg, custom_set) {

    //Default
    var set = {
        message_class: 'splashmsg default',
        fadein_sec: 0.1,
        wait_sec: 2,
        fadeout_sec: 1.5,
        opacity: 0.9,
        trans_in: 'ease-in',
        trans_out: 'ease-out',
        outer_style: 'top: 0px;left: 0px;position: fixed;z-index: 1000;width: 100%;height: 100%;',
        message_style: 'padding:0.5em;font-size:1em;color:white;background-color:gray; position: absolute;top: 10%; left: 85%;transform: translateY(-50%) translateX(-50%);-webkit-transform: translateY(-50%) translateX(-50%);',
        style_id: 'append_splash_msg_style',
        outer_id: 'append_splash_msg',
        message_id: 'append_splash_msg_inner',
        on_splash_vanished: null //callback function
    };

    //Override custom_set
    for (var key in custom_set) {
        if (custom_set.hasOwnProperty(key)) {
            set[key] = custom_set[key];
        }
    }

    //Style
    if (!document.getElementById(set.style_id)) {
        var style = document.createElement('style');
        style.id = set.style_id;
        style.innerHTML =
            "#" + set.outer_id + " { " + set.outer_style + " } " +
            "#" + set.outer_id + " > #" + set.message_id + " {opacity: 0;transition: opacity " + set.fadeout_sec + "s " + set.trans_out + ";-webkit-transition: opacity " + set.fadeout_sec + "s " + set.trans_out + ";} " +
            "#" + set.outer_id + ".show > #" + set.message_id + " {opacity: " + set.opacity + ";transition: opacity " + set.fadein_sec + "s " + set.trans_in + ";-webkit-transition: opacity " + set.fadein_sec + "s " + set.trans_in + ";}" +
            "#" + set.message_id + " { " + set.message_style + " } ";
        document.body.appendChild(style);
    }

    //Element (Outer, Inner)
    if ((e = document.getElementById(set.outer_id))) { e.parentNode.removeChild(e); if (set.on_splash_vanished) set.on_splash_vanished(); }
    var splash = document.createElement('div');
    splash.id = set.outer_id;
    splash.onclick = function () {
        if ((e = document.getElementById(set.outer_id))) e.parentNode.removeChild(e);
        if (set.on_splash_vanished) set.on_splash_vanished();
    };
    splash.innerHTML = '<div id="' + set.message_id + '" class="' + set.message_class + '">' + msg + '</div>';
    document.body.appendChild(splash);

    //Timer
    setTimeout(function () { if (splash) splash.classList.add('show'); }, 0);
    setTimeout(function () { if (splash) splash.classList.remove('show'); }, set.wait_sec * 1000);
    setTimeout(function () { if (splash && splash.parentNode) splash.parentNode.removeChild(splash); if (set.on_splash_vanished) set.on_splash_vanished(); }, (set.fadeout_sec + set.wait_sec) * 1000);

};
```

次に、Controller側にPostを受信する実装を行います。
が、まず座標情報を受け取るインターフェイスが必要となります。

```Models\Interface\CutData.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace sample2_1.Models.Interface
{
    public class CutData
    {
        public string img_path { get; set; }
        public float x { get; set; }
        public float y { get; set; }
        public float width { get; set; }
        public float height { get; set; }
    }
}
```

このクラスをFormBodyとして定義することで、受け取ることが出来ます。

```Controllers\WorkController.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Amazon;
using Amazon.S3;
using Amazon.S3.Transfer;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using sample2_1.Models;
using sample2_1.Models.Const;
using sample2_1.Models.Interface;

namespace sample2_1.Controllers
{
    public class WorkController : Controller
    {
        // AmazonS3 インターフェイス
        private static IAmazonS3 client;

        public IActionResult Index()
        {
            -略-
        }

        [HttpPost("UploadFiles")]
        public async Task<IActionResult> FileUpload(List<IFormFile> files)
        {
            -略-
        }

        // S3 ストレージへアップロード！
        static async Task WritingAnObjectAsync(string s_upd_file_nm)
        {
            -略-
        }

        //asp.net coreでサービス作ったから宣伝兼ねて技術公開する -2_6.分割画面→Canvasオブジェクト- 追加
        public IActionResult Canvas() 
        {
            -略-
        }


        [HttpPost]
        //FormからJsonを受け取り、クラス変換してくれます。
        public string Cut([FromBody]CutData cutData)
        {
            
            // 受け取ったheightプロパティを画面に返す。
            return cutData.height.ToString();
        }
    }
}
```

一旦これで実行してみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/249830/86d17962-524f-9622-cade-bf6e7693da13.png)

右上の方に、範囲指定した高さの値が表示されました。
これで準備はOK。

Controllerでの実装に入ります。

##画像の分割処理するための、前準備（2）
情報は全部揃ったので、画像処理へ入ります。
と言いたかったのですが、asp.net core 2系なのかな？

Imageクラスが無いのです。
Imageクラスなしに画像加工は考えられません。
さっそく探しましょう。

参考サイト：
.NET Core で画像を縮小する
　http://www.moonmile.net/blog/archives/9262

NUGETから、「System.Drawing.Common」をインストール

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/249830/19ebb73b-5b8b-12ee-faf0-3b5c21a0b755.png)

すると、Imageクラスが使用可能になります。


##画像の分割処理

さすがにお待たせしました。
やっと実装部です。



```Controllers\WorkController.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Amazon;
using Amazon.S3;
using Amazon.S3.Transfer;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using sample2_1.Models;
using sample2_1.Models.Const;
using sample2_1.Models.Interface;

namespace sample2_1.Controllers
{
    public class WorkController : Controller
    {
        // AmazonS3 インターフェイス
        private static IAmazonS3 client;

        public IActionResult Index()
        {
            -略-
        }

        [HttpPost("UploadFiles")]
        public async Task<IActionResult> FileUpload(List<IFormFile> files)
        {
            -略-
        }

        // S3 ストレージへアップロード！
        static async Task WritingAnObjectAsync(string s_upd_file_nm)
        {
            -略-
        }

        //asp.net coreでサービス作ったから宣伝兼ねて技術公開する -2_6.分割画面→Canvasオブジェクト- 追加
        public IActionResult Canvas() 
        {
            -略-
        }


        [HttpPost]
        //FormからJsonを受け取り、クラス変換してくれます。
        public string Cut([FromBody]CutData cutData)
        {
            // 1.Imageへ画面に表示している画像ファイルを読み込み
            Image image = null;
            try
            {
                image = Image.FromFile(@"C:\work\work.jpg");
            }
            catch (Exception e)
            {
                throw e;
            }

            // 2.Bitmapに範囲指定したサイズを設定
            Bitmap destbmp = new Bitmap((int)cutData.width, (int)cutData.height);
            // 3.Graphicsに読み込ませるBitmapを設定
            Graphics graphics = Graphics.FromImage(destbmp);
            // 4.[1]でロードした画像を描画
            graphics.DrawImage(image, (cutData.x * -1) , (cutData.y * -1), image.Size.Width, image.Size.Height);
            // 5.保存先の指定
            var stream = new FileStream(@"C:\work\cutter.jpg", FileMode.Create);
            // 6.保存処理
            destbmp.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg);
            // 7.後処理
            stream.Close();

            // 受け取ったheightプロパティを画面に返す。
            return @"正常にCut出来ました！";
        }

    }
}
```

[4]の部分だけ、補足説明します。
分かってしまえば簡単なのですが、最初はちょっと理解しにくかったので、、、え？俺だけ？

Imageをロードして、座標指定して分割するのではなく、
Bitmapを用意して、予めロードしたImageを座標指定（マイナス値）して読み込ませ、Bitmapの部分だけをロードする方法です。
色々関数とか探したのですが、これが一番シンプルでした。
メモリはいっぱい使うかも。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/249830/bfe97f22-3501-9a7c-6474-0fd49d96e9b7.png)



それでは、実行してみましょう！

##デバッグ実行

マウスで、範囲選択。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/249830/9558d751-9d5b-0595-0fc2-4563ef4986d5.png)

Enter！ターン！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/249830/3bb8dc7b-2829-3e4a-2b0d-432a6c333f86.png)

正常にCut出来たそうです。
ってことで、 C:\Work\cutter.jpg を見てみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/249830/3f27dcd8-8c81-ee48-4aed-7f230617824d.png)

ファイルが作成されていることが確認。
画像はこんな感じ。

![cutter.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/249830/e8dc35da-5577-ac29-3bde-ed49798902ea.jpeg)


まぁこんなとこですね。

最後に今回のソースコード貼っておきます。


```Views\Work\Canvas.cshtml
@{
    ViewData["Title"] = "Canvas Page";
}

<style type="text/css">
    canvas {
        border: solid 3px #000;
    }
</style>

<canvas id="SrcCanvas"
        style="height:90%;width:100%;"></canvas>

<script src="~/js/canvas.js"></script>
<script type="text/javascript">
    canvas_init("/img/work.jpg");
</script>
```

```wwwroot\js\canvas.js
// キャンバス
var src_canvas;
var src_ctx;

// 矩形用
var rect_MousedownFlg = false;
var rect_fx = 0;
var rect_fy = 0;
var rect_ex = 0;
var rect_ey = 0;

// イメージ
var img = new Image();

// 座標補正係数
var x_keisu = 1.0;
var y_keisu = 1.0;

// 棚SEQ
var tana_seq = 0;

// 初期処理
function canvas_init(img_src) {
    src_canvas = document.getElementById("SrcCanvas");
    src_ctx = src_canvas.getContext("2d");

    /* Imageオブジェクトを生成 */
    img.src = img_src;

    /* 画像が読み込まれるのを待ってから処理を続行 */
    img.onload = function () {

        src_canvas.width = img.width;
        src_canvas.height = img.height;

        src_ctx.drawImage(img, 0, 0);
    }

    // イベントを追加
    src_canvas.setAttribute('tabindex', 0);
    src_canvas.addEventListener('mousedown', mousedown, false);
    src_canvas.addEventListener('mousemove', mousemove, false);
    src_canvas.addEventListener('mouseup', mouseup, false);
    src_canvas.addEventListener('keydown', keydown, false);
}

// マウスクリック時
function mousedown(event) {

    rect_MousedownFlg = true;
    src_canvas = document.getElementById("SrcCanvas");
    var rect = event.target.getBoundingClientRect();

    // 係数を算出
    x_keisu = src_canvas.width / rect.width;
    y_keisu = src_canvas.height / rect.height;


    // 座標を求める
    rect_fx = (event.clientX - src_canvas.offsetLeft) * x_keisu;
    rect_fy = (event.clientY - src_canvas.offsetTop) * y_keisu;

    // 矩形の枠色を反転させる  
    var imagedata = src_ctx.getImageData(rect_fx, rect_fy, 1, 1);
    src_ctx.strokeStyle = 'rgb(' + getTurningAround(imagedata.data[0]) +
        ',' + getTurningAround(imagedata.data[1]) +
        ',' + getTurningAround(imagedata.data[2]) + ')';
    // 線の太さ                         
    src_ctx.lineWidth = 6;

    // 矩形の枠線を点線にする
    src_ctx.setLineDash([2, 3]);

}

// マウス移動時
function mousemove(event) {
    if (rect_MousedownFlg) {

        // 座標を求める
        var rect = event.target.getBoundingClientRect();
        rect_ex = (event.clientX - src_canvas.offsetLeft) * x_keisu;
        rect_ey = (event.clientY - src_canvas.offsetTop) * y_keisu;

        // 元画像の再描画
        src_ctx.drawImage(img, 0, 0);

        // 矩形の描画
        src_ctx.beginPath();

        // 上
        src_ctx.moveTo(rect_fx, rect_fy);
        src_ctx.lineTo(rect_ex, rect_fy);

        // 下
        src_ctx.moveTo(rect_fx, rect_ey);
        src_ctx.lineTo(rect_ex, rect_ey);

        // 右
        src_ctx.moveTo(rect_ex, rect_fy);
        src_ctx.lineTo(rect_ex, rect_ey);

        // 左
        src_ctx.moveTo(rect_fx, rect_fy);
        src_ctx.lineTo(rect_fx, rect_ey);

        src_ctx.stroke();
    }
}

// マウス移動時
function mouseup(event) {
    rect_MousedownFlg = false;

    // 座標を求める
    var rect = event.target.getBoundingClientRect();
    rect_ex = (event.clientX - src_canvas.offsetLeft) * x_keisu;
    rect_ey = (event.clientY - src_canvas.offsetTop) * y_keisu;


    // 青
    src_ctx.strokeStyle = 'rgb(255,255,0)';
    // 線の太さ                         
    src_ctx.lineWidth = 10;
    // 矩形の描画
    src_ctx.beginPath();
    // 上
    src_ctx.moveTo(0, 0);
    src_ctx.lineTo(img.width, 0);
    src_ctx.stroke();
}

// 色の反転
function getTurningAround(color) {
    // 灰色は白にする 
    if (color >= 88 && color <= 168) {
        return 255;
        // 色を反転する  
    } else {
        return 255 - color;
    }
}



// 範囲指定後
function keydown(event) {

    if (event.keyCode != 13) {
        return;
    }

    var v_x;
    var v_y;
    var v_w;
    var v_h;
    if (rect_ex - rect_fx > 0) {
        v_x = rect_fx;
        v_w = rect_ex - rect_fx;
    } else {
        v_x = rect_ex;
        v_w = rect_fx - rect_ex;
    }

    if (rect_ey - rect_fy > 0) {
        v_y = rect_fy;
        v_h = rect_ey - rect_fy;
    } else {
        v_y = rect_ey;
        v_h = rect_fy - rect_ey;
    }

    // うまく取れない場合、エラーとする。
    if (v_w <= 0 || v_h <= 0) {
        return;
    }

    var j = {
        img_path: img.src
        , x: v_x
        , y: v_y
        , width: v_w
        , height: v_h
    };

    var jsonString = JSON.stringify(j);

    $.ajax({
        type: "POST", // 省略可（省略時は"GET"）
        url: "/Work/Cut",
        traditional: true,
        contentType: "application/json; charset=utf-8",
        data: jsonString,
        cache: false, // 省略可（省略時はtrue）
        timeout: 120000 // 省略可
    })
        // 成功時
        .done(function (data, textStatus, jqXHR) {
            splash(data);
        })
        // 失敗時
        .fail(function (jqXHR, textStatus, errorThrown) {
            splash("正常に処理が行なえませんでした。");
        });
}


splash = function (msg, custom_set) {

    //Default
    var set = {
        message_class: 'splashmsg default',
        fadein_sec: 0.1,
        wait_sec: 2,
        fadeout_sec: 1.5,
        opacity: 0.9,
        trans_in: 'ease-in',
        trans_out: 'ease-out',
        outer_style: 'top: 0px;left: 0px;position: fixed;z-index: 1000;width: 100%;height: 100%;',
        message_style: 'padding:0.5em;font-size:1em;color:white;background-color:gray; position: absolute;top: 10%; left: 85%;transform: translateY(-50%) translateX(-50%);-webkit-transform: translateY(-50%) translateX(-50%);',
        style_id: 'append_splash_msg_style',
        outer_id: 'append_splash_msg',
        message_id: 'append_splash_msg_inner',
        on_splash_vanished: null //callback function
    };

    //Override custom_set
    for (var key in custom_set) {
        if (custom_set.hasOwnProperty(key)) {
            set[key] = custom_set[key];
        }
    }

    //Style
    if (!document.getElementById(set.style_id)) {
        var style = document.createElement('style');
        style.id = set.style_id;
        style.innerHTML =
            "#" + set.outer_id + " { " + set.outer_style + " } " +
            "#" + set.outer_id + " > #" + set.message_id + " {opacity: 0;transition: opacity " + set.fadeout_sec + "s " + set.trans_out + ";-webkit-transition: opacity " + set.fadeout_sec + "s " + set.trans_out + ";} " +
            "#" + set.outer_id + ".show > #" + set.message_id + " {opacity: " + set.opacity + ";transition: opacity " + set.fadein_sec + "s " + set.trans_in + ";-webkit-transition: opacity " + set.fadein_sec + "s " + set.trans_in + ";}" +
            "#" + set.message_id + " { " + set.message_style + " } ";
        document.body.appendChild(style);
    }

    //Element (Outer, Inner)
    if ((e = document.getElementById(set.outer_id))) { e.parentNode.removeChild(e); if (set.on_splash_vanished) set.on_splash_vanished(); }
    var splash = document.createElement('div');
    splash.id = set.outer_id;
    splash.onclick = function () {
        if ((e = document.getElementById(set.outer_id))) e.parentNode.removeChild(e);
        if (set.on_splash_vanished) set.on_splash_vanished();
    };
    splash.innerHTML = '<div id="' + set.message_id + '" class="' + set.message_class + '">' + msg + '</div>';
    document.body.appendChild(splash);

    //Timer
    setTimeout(function () { if (splash) splash.classList.add('show'); }, 0);
    setTimeout(function () { if (splash) splash.classList.remove('show'); }, set.wait_sec * 1000);
    setTimeout(function () { if (splash && splash.parentNode) splash.parentNode.removeChild(splash); if (set.on_splash_vanished) set.on_splash_vanished(); }, (set.fadeout_sec + set.wait_sec) * 1000);

};
```

```Controllers\WorkController.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Drawing;
using Amazon;
using Amazon.S3;
using Amazon.S3.Transfer;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using sample2_1.Models;
using sample2_1.Models.Const;
using sample2_1.Models.Interface;

namespace sample2_1.Controllers
{
    public class WorkController : Controller
    {
        // AmazonS3 インターフェイス
        private static IAmazonS3 client;

        public IActionResult Index()
        {
            /* ログインされている場合、共通受け渡しデータにユーザ名を入れておく */
            if (User.Identity.IsAuthenticated)
            {
                var user_id = "";
                var user_nm = "";
                // ユーザIDの特定
                foreach (var i in this.User.Identities)
                {
                    foreach (var n in i.Claims)
                    {
                        if (n.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")
                        {
                            user_id = n.Value;
                        }
                        if (user_id != "") break;
                    }
                    if (user_id != "") break;
                }

                // ユーザマスタから情報取得
                using (var db = new AppDbContext())
                {
                    // [出力]
                    foreach (var mUser in db.MUsers.Where(m => m.UserId == user_id))
                    {
                        user_nm = mUser.UserNm;
                        break;
                    }
                }

                ViewData["user_nm"] = user_nm;
            }



            return View();
        }

        [HttpPost("UploadFiles")]
        public async Task<IActionResult> FileUpload(List<IFormFile> files)
        {
            // 保存先を取得
            string filePath = @"C:\work\";

            // Amazon S3 キーを取得(準備で取得したキーを設定してください。)
            // このキーがバレると、勝手に使われてしまう可能性があるので、お気をつけて
            var accessKey = ConstKey.AWS_KEY;
            var accessSecretKey = ConstKey.AWS_SECRET_KEY;

            // ファイル保存
            long size = files.Sum(f => f.Length);
            foreach (var formFile in files)
            {
                if (formFile.Length <= 0) continue;
                // ローカルに保存
                using (var stream = new FileStream(filePath + formFile.FileName, FileMode.Create))
                {
                    await formFile.CopyToAsync(stream);
                }

                // Amazon S3 クライアントの設定
                client = new AmazonS3Client(accessKey
                                           , accessSecretKey
                                           , RegionEndpoint.USEast2);
                // 非同期メソッドの実行
                WritingAnObjectAsync(formFile.FileName).Wait();


            }

            // 画面に返す返却値
            ViewData["uploadResult"] = Ok(new { count = files.Count, size, filePath }).Value.ToString();

            // ViewをIndex画面で返却
            return View("Index");
        }

        // S3 ストレージへアップロード！
        static async Task WritingAnObjectAsync(string s_upd_file_nm)
        {

            // S3の保存先を設定
            string S3BucketName = @"tanatoru-s3";
            string S3Key = @"work/";

            // 元のファイル名をそのまま利用
            S3Key += s_upd_file_nm;

            try
            {
                // キーを設定したclientを引数に、アップロードクラスを取得
                var fileTransferUtility = new TransferUtility(client);

                // アップロード処理
                using (var fileToUpload =
                    new FileStream(@"c:\work\" + s_upd_file_nm, FileMode.Open, FileAccess.Read))
                {
                    await fileTransferUtility.UploadAsync(fileToUpload
                                                        ,S3BucketName
                                                        ,S3Key);
                }
                // OK！
                Console.WriteLine("Upload 3 completed");

            }
            catch (AmazonS3Exception e)
            {
                Console.WriteLine(
                        "Error encountered ***. Message:'{0}' when writing an object"
                        , e.Message);
            }
            catch (Exception e)
            {
                Console.WriteLine(
                    "Unknown encountered on server. Message:'{0}' when writing an object"
                    , e.Message);
            }
        }

        //asp.net coreでサービス作ったから宣伝兼ねて技術公開する -2_6.分割画面→Canvasオブジェクト- 追加
        public IActionResult Canvas()
        {
            return View();
        }


        [HttpPost]
        //FormからJsonを受け取り、クラス変換してくれます。
        public string Cut([FromBody]CutData cutData)
        {
            // 1.Imageへ画面に表示している画像ファイルを読み込み
            Image image = null;
            try
            {
                image = Image.FromFile(@"C:\work\work.jpg");
            }
            catch (Exception e)
            {
                throw e;
            }

            // 2.Bitmapに範囲指定したサイズを設定
            Bitmap destbmp = new Bitmap((int)cutData.width, (int)cutData.height);
            // 3.Graphicsに読み込ませるBitmapを設定
            Graphics graphics = Graphics.FromImage(destbmp);
            // 4.[1]でロードした画像を描画
            graphics.DrawImage(image, (cutData.x * -1) , (cutData.y * -1), image.Size.Width, image.Size.Height);
            // 5.保存先の指定
            var stream = new FileStream(@"C:\work\cutter.jpg", FileMode.Create);
            // 6.保存処理
            destbmp.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg);
            // 7.後処理
            stream.Close();

            // 受け取ったheightプロパティを画面に返す。
            return @"正常にCut出来ました！";
        }

    }
}
```

---

【是非お試しください】※完全無料です
TanaToru -本棚管理サービス-
　https://app.zero-one-system.co.jp/TanaToru/