SpringBoot で RestAPI で写真とって画像の受け渡しやってみる

SpringBoot で RestAPI で画像の受け渡し時のメモ
画面側から fetch で post で body につめて送る感じでやってみる
受けた後の処理はご自由にで一旦は json でもらったものをそのまま返すだけにしてます

# 実行環境
- Windows11
- Pleiades 2022 Full Edition
- Maven
- SpringFramework Boot v3.1.5
- Java v17
- spring-boot-starter-thymeleaf
- spring-boot-starter-web

# プロジェクト依存関係
雑に pom.xml の中身です
```XML
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.1.5</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>springboot-maven2</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>springboot-maven2</name>
	<description>Demo project for Spring Boot 3.0.0</description>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

```

# コントローラ作成
コントローラとして2つ作成する
- 画面表示用 `HomeController`
- 画像RestAPI `TakeController`

### 画面表示用 `HomeController`
```java:HomeController.java
package samp.cont;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {

    @GetMapping("/home")
    public String home() {
        return "home";
    }

}
```

### 画像RestAPI `TakeController`
post の受け取り方は DTO みたいなクラス作るパターンと Map で受ける2パターン作ってます
```java:TakeController.java
package samp.cont;

import java.util.Map;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TakeController {

    // こっちは DTO で受けるパターン
    @PostMapping("/take1")
    public Take take1(@RequestBody Take take) {
        return take;
    }

    // こっちは Map で受けるパターン
    @PostMapping("/take2")
    public Map<String, String> take2(@RequestBody Map<String, String> take) {
        return take;
    }

}
```

こちらは DTO として作った Take クラス
```java:Take.java
package samp.cont;

import lombok.Data;

@Data
public class Take {
    private String url;
}
```

# JavaScript 作成
デバイスのカメラ設定とhtmlのDOMへの関連付けを initVideoCamera で処理してます
撮影して ReasAPI 実行のところを photoShoot で処理してます

```javascript:script.js
const video = document.querySelector('#video');
const canvas = document.createElement('canvas');

initVideoCamera();
document.querySelector('#shoot1').addEventListener('click', () => photoShoot('take1'));
document.querySelector('#shoot2').addEventListener('click', () => photoShoot('take22'));

// ビデオのカメラ設定(デバイスのカメラ映像をビデオに表示)
function initVideoCamera() {
    navigator.mediaDevices.getUserMedia({ video: true, audio: false })
        .then((stream) => {
            video.srcObject = stream;
            video.play();
        })
        .catch(e => console.log(e));
}

// カメラ撮影して RestAPI 実行
function photoShoot(take) {
    let drawSize = calcDrawSize();
    canvas.width = drawSize.width;
    canvas.height = drawSize.height;
    const context = canvas.getContext("2d");
    context.drawImage(video, 0, 0, canvas.width, canvas.height);

    fetch(`/${take}`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({url: canvas.toDataURL("image/png")}),
    }).then(res => res.json()).then(json=>{
        document.querySelector("#photo").src = json.url;
    });
}

// 描画サイズの計算
// 縦横比が撮影(video)が大きい時は撮影の縦基準、それ以外は撮影の横基準で計算
function calcDrawSize() {
    let videoRatio = video.videoHeight / video.videoWidth;
    let viewRatio = video.clientHeight / video.clientWidth;
    return videoRatio > viewRatio ?
        { height: video.clientHeight, width: video.clientHeight / videoRatio }
        : { height: video.clientWidth * videoRatio, width: video.clientWidth }
}
```

# HTML 作成
こちらは細かいこと気にしない
カメラの映像表示する枠と撮影した後の画像表示する枠を準備
```html:home.html
<!DOCTYPE html>
<html>
<head>
    <title>Take Sample</title> 
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <link rel="stylesheet" th:href="@{style.css}" >
</head>
<body>
    <div class="flex-row">
        <div class="camera">
            <video id="video"></video>
            <div id="shoot1" class="shoot">撮影1</div>
            <div id="shoot2" class="shoot">撮影2</div>
        </div>
        <img id="photo">
    </div>
</body>
<script th:src="script.js"></script>
</html>
```

# CSS 作成
こちらも細かいこと気にしない
適当にととのえる感じです
```css:style.css
@charset "utf-8";

body {
  text-align: center;
}

.flex-row {
  display: flex;
  justify-content: center;
}

#video {
  border: 1px solid black;
  width: 320px;
  height: 240px;
}

#photo {
  border: 1px solid black;
  width: 320px;
  height: 240px;
}

.shoot {
  width: 100px;
  margin: 5px auto;
  border: solid 1px skyblue;
  border-radius: 30px;
  background-color: skyblue;
}

.shoot:hover {
  opacity: 0.7;
}

p.ret-element {
  margin-top: 0;
}

@media (width <=1000px) {
  .flex-row {
    display: block;
  }
}
```

# 実行して起動して確認
撮影おして写真が描画されればOK
RestAPI のコントローラ側でなんか処理して保存とかしても良いかも
実行画面はこんな感じ．．．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51758/a3cc1dfe-5176-50e1-f90c-b85f599d2100.png)