Go言語の標準パッケージだけで画像処理をする その２ （回転、反転）

[ZOZOテクノロジーズ #5 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech5)の記事です。
昨日は 「[Go言語の標準パッケージだけで画像処理をする その１ （入出力）](https://qiita.com/ikeponsu/items/808a83d2f3bcf3ad95f3)」 の記事を書きました。

本記事では引き続き、Go言語の標準パッケージでの画像処理について書いていきます。

# はじめに

「なぜGo言語で画像処理をするのか？ということについては、以下の記事をご覧ください。
[Go言語の標準パッケージだけで画像処理をする その１ （入出力）](https://qiita.com/ikeponsu/items/808a83d2f3bcf3ad95f3)

![CEC2C8A6-4EE6-414F-8191-E256170FC0E1.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308887/a009c422-02b6-7045-782e-ba4c79ff09ab.jpeg)


Go言語 image パッケージ
[Package image](https://golang.org/pkg/image/)

# 回転

ここではアフィン変換を用いて画像の回転を実装しています。
アフィン変換の詳細については以下を参照していただければと思います。

[画像処理ソリューション アフィン変換](https://imagingsolution.blog.fc2.com/blog-entry-284.html)

以下ソースコードと出力画像になります。

```Go
// 回転の処理
func Rotation(inputImage image.Image, mode int) image.Image {

	// 出力画像を定義
	var outputImage image.Image

	switch mode {
	case -1:
		// 右90度回転
		outputImage = Affine(inputImage, 90, inputImage.Bounds().Max.X - 1, 0, 1)
		break
	case 0:
		// 右180度回転
		outputImage = Affine(inputImage, 180, inputImage.Bounds().Max.X - 1, inputImage.Bounds().Max.Y - 1, 1)
		break
	case 1:
		// 右270度回転
		outputImage = Affine(inputImage, 270, 0, inputImage.Bounds().Max.Y - 1, 1)
		break
	default:
		log.Fatal("angle code does not exist")
		break
	}

	return outputImage
}

// アフィン変換の処理
func Affine(inputImage image.Image, angle int, tx int, ty int, scale float64) image.Image {

	// 出力画像を定義
	size := inputImage.Bounds()
	size.Max.X = int(float64(size.Max.X) * scale)
	size.Max.Y = int(float64(size.Max.Y) * scale)

	outputImage := image.NewRGBA(size)

	// ステータスのキャスト
	theta := float64(angle) * math.Pi / 180
	cos := math.Cos(theta)
	sin := math.Sin(theta)

	matrix := [][]float64{{cos * scale, -sin * scale, float64(tx)}, {sin * scale, cos * scale, float64(ty)}, {0.0, 0.0, 1.0}}

	// 左右反転
	for y := size.Min.Y; y < size.Max.Y; y++ {
		for x := size.Min.X; x < size.Max.X; x++ {

			outputX := 0
			outputY := 0
			// 元座標を格納
			origin := []float64{float64(x), float64(y), 1.0}

			// 座標を計算
			for rowKey, rowVal := range matrix {
				var val float64

				for colIndex := 0; colIndex < len(rowVal); colIndex++ {

					val += origin[colIndex] * rowVal[colIndex]
				}

				// 座標の代入
				switch rowKey {
				case 0:
					outputX = int(round(val))
					break
				case 1:
					outputY = int(round(val))
					break
				default:
					break
				}

			}

			if size.Min.X <= outputX && outputX < size.Max.X && size.Min.Y <= outputY && outputY < size.Max.Y {
				outputImage.Set(outputX, outputY, inputImage.At(x, y))
			} else {
				// 何もしない
			}
		}
	}

	return outputImage
}
```
## 出力結果
### 元画像
![Lennagrey.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308887/1ebfb8e2-9ed9-746e-0498-1fd4d3f4de90.png)

### 右90度回転
![LennagreyR90.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308887/614fb8a7-3ae9-685c-9f3a-6c2767a1a8e7.png)

### 右180度
![LennagreyR180.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308887/6083be07-afac-6b72-cdeb-bb993ca41261.png)

### 右270度回転
![LennagreyR270.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308887/5febbdd7-1da9-0e49-8721-ff98d4bdbdab.png)

# 反転

画像をフィルタ処理にかける前の前処理で使いたかったので実装しました。
やっていることとしては、画素値を移動させているだけです。

以下ソースコードと出力画像になります。

``` Go
// 画像を反転させる処理
func Inversion(inputImage image.Image, mode int) image.Image {

	// 出力画像を定義
	var outputImage image.Image

	switch mode {
		case -1:
			// 上下左右反転
			outputImage = upsideDown(inputImage)
			break
		case 0:
			// 上下反転
			outputImage = flipUpsideDown(inputImage)
			break
		case 1:
			// 左右反転
			outputImage = flipHorizontal(inputImage)
			break
		default:
			log.Fatal("angle code does not exist")
			break
	}

	return outputImage

}

// 左右反転の処理
func flipHorizontal(inputImage image.Image) image.Image {

	// 出力画像を定義
	size := inputImage.Bounds()
	outputImage := image.NewRGBA(size)

	// 左右反転
	for y := size.Min.Y; y < size.Max.Y; y++ {
		for x := size.Min.X; x < size.Max.X; x++ {
			outputImage.Set(x, y, inputImage.At(size.Max.X - x - 1, y))
		}
	}

	return outputImage
}

// 上下反転の処理
func flipUpsideDown(inputImage image.Image) image.Image {

	// 出力画像を定義
	size := inputImage.Bounds()
	outputImage := image.NewRGBA(size)

	// 上下反転
	for y := size.Min.Y; y < size.Max.Y; y++ {
		for x := size.Min.X; x < size.Max.X; x++ {
			outputImage.Set(x, y, inputImage.At(x, size.Max.Y - y -1))
		}
	}

	return outputImage
}

// 上下左右反転の処理
func upsideDown(inputImage image.Image) image.Image {

	// 左右反転
	outputImage := flipHorizontal(inputImage)
	// 上下反転
	outputImage = flipUpsideDown(outputImage)

	return outputImage
}
```
## 出力結果
### 元画像
![Lennagrey.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308887/635d58c8-ebf9-4474-9038-947d6b29f41c.png)

### 上下左右反転
![Lennagrey上下左右.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308887/88ec0d86-8c7a-3cdf-a29e-81ea45dff596.png)

### 左右反転
![Lennagrey左右.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308887/a4bd5639-c434-9b5f-118f-b8c192a83626.png)

### 上下反転
![Lennagrey上下.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308887/7593306f-faa7-d3a9-6d6e-fee98313778f.png)


# おわりに

[ZOZOテクノロジーズ #5 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech5) 明日は @katsuyan さんによる「Digdagで大きいパラメータを登録すると後続の処理が重くなる」です。