gitbook/朱涛 · Kotlin编程第一课/docs/489708.md
2022-09-03 22:05:03 +08:00

183 lines
6.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 期中考试 | 用Kotlin实现图片处理程序
你好,我是朱涛。不知不觉间,咱们的课程就已经进行一半了,我们已经学完很多内容:
* 基础篇我们学完了所有Kotlin基础语法和重要特性。
* 加餐篇我们学习了Kotlin编程的5大编程思维函数式思维、表达式思维、不变性思维、空安全思维、协程思维。
* 协程篇,我们也已经学完了所有基础的协程概念。
所以现在,是时候来一次阶段性的验收了。这次,我们一起来做一个**图片处理程序**来考察一下自己对于Kotlin编程知识的理解和应用掌握情况。初始化工程的代码在这里[GitHub](https://github.com/chaxiu/ImageProcessor.git)你可以像往常那样将其clone下来然后用IntelliJ打开即可。
我们仍然会分为两个版本1.0、2.0,不过,这一次要轮到你亲自动手写代码了!
## 1.0版本:处理本地图片
当你将初始化工程打开以后你会发现“src/main/resources/images/”这个目录下有一张图片android.png它就是我们要处理的图片对象。
![图片](https://static001.geekbang.org/resource/image/0d/64/0de4da2977353d97631d88531feff464.png?wh=1817x704)
一般来说我们想要处理图片会第一时间想到Photoshop但其实简单的图片处理任务我们完全可以通过代码来实现比如图片横向翻转、图片纵向翻转、图片裁切。
![图片](https://static001.geekbang.org/resource/image/45/c6/456e395f69c12b20e095959046fccac6.png?wh=1128x424)
关于图片的底层定义Java SDK已经提供了很好的支持我们在Kotlin代码当中可以直接使用相关的类。为了防止你对JDK不熟悉我在初始化工程当中已经为你做好了前期准备工作
```plain
class Image(private val pixels: Array<Array<Color>>) {
fun height(): Int {
return pixels.size
}
fun width(): Int {
return pixels[0].size
}
/**
* 底层不处理越界
*/
fun getPixel(y: Int, x: Int): Color {
return pixels[y][x]
}
}
```
这是我定义的一个Image类它的作用就是封装图片的内存对象。我们都知道图片的本质是一堆像素点Pixel而每个像素点都可以用RGB来表示这里我们用Java SDK当中的Color来表示。
当我们把图片放大到足够倍数的时候,我们就可以看到其中的**正方形像素点**了。
![图片](https://static001.geekbang.org/resource/image/4a/a2/4a833f282d7f56e6c10707f9b36yy4a2.png?wh=1489x862)
所以,最终我们就可以用“`Array<Array<Color>>`”这样一个二维数组来表示一张图片。
另外,从本地加载图片到内存的代码,我也帮你写好了:
```plain
const val BASE_PATH = "./src/main/resources/images/"
fun main() {
val image = loadImage(File("${BASE_PATH}android.png"))
println("Width = ${image.width()};Height = ${image.height()}")
}
/**
* 加载本地图片到内存中
*/
fun loadImage(imageFile: File) =
ImageIO.read(imageFile)
.let {
Array(it.height) { y ->
Array(it.width) { x ->
Color(it.getRGB(x, y))
}
}
}.let {
Image(it)
}
```
那么,唯一需要你做的,就是实现这几个函数的功能:**图片横向翻转、图片纵向翻转、图片裁切**。
```plain
/**
* 横向翻转图片
* 待实现
*/
fun Image.flipHorizontal(): Image = TODO()
/**
* 纵向翻转图片
* 待实现
*/
fun Image.flipVertical(): Image = TODO()
/**
* 图片裁切
* 待实现
*/
fun Image.crop(startY: Int, startX: Int, width: Int, height: Int): Image = TODO()
```
另外,如果你有兴趣的话,还可以去实现对应的单元测试代码:
```plain
class TestImageProcessor {
/**
* 待实现的单元测试
*/
@Test
fun testFlipHorizontal() {
}
/**
* 待实现的单元测试
*/
@Test
fun testFlipVertical() {
}
/**
* 待实现的单元测试
*/
@Test
fun testCrop() {
}
}
```
这样一来我们1.0版本的代码就算完成了。不过,我们还没用上协程的知识啊!
请看2.0版本。
## 2.0版本:增加图片下载功能
在上个版本中,我们的代码仅支持本地图片的处理,但有的时候,我们想要处理网络上的图片该怎么办呢?所以这时候,我们可以增加一个**下载网络图片的功能**。
这个版本,你只需要实现一个函数:
```plain
/**
* 挂起函数以http的方式下载图片保存到本地
* 待实现
*/
suspend fun downloadImage(url: String, outputFile: File): Boolean = TODO()
```
需要注意的是,由于下载网络图片比较耗时,我们需要将其定义成一个**挂起函数**,这样一来,我们后续在使用它的时候就可以更得心应手了。
```plain
fun main() = runBlocking {
// 不一定非要下载我提供的链接
val url = "https://raw.githubusercontent.com/chaxiu/ImageProcessor/main/src/main/resources/images/android.png"
val path = "${BASE_PATH}downloaded.png"
// 调用挂起函数
downloadImage(url, File(path))
val image = loadImage(File(path))
println("Width = ${image.width()};Height = ${image.height()}")
}
```
在上面的代码中,我是以“[https://raw.githubusercontent.com/chaxiu/ImageProcessor/main/src/main/resources/images/android.png”](https://raw.githubusercontent.com/chaxiu/ImageProcessor/main/src/main/resources/images/android.png%E2%80%9D) 这个链接为例这是一个HTTPS的链接你在实际开发的时候也可以随便去找一个普通的HTTP图片链接这样就不必处理SSL的问题了。
程序实际运行效果会是这样的:
![图片](https://static001.geekbang.org/resource/image/e7/71/e7b549e6e97cffdd67e8379004773171.gif?wh=1026x764)
在下节课里,我会给出我的代码参考,不过在看我的代码之前,记得先要自己动手啊。
其实以我们这个工程为基础再加上一些图形学算法我们完全可以做出Photoshop当中的一些高级功能比如图片缩放、图片参数调节、图片滤镜、抠像甚至图片识别等等。如果你本身就有图形学方面的知识储备也欢迎你在此基础上实现更复杂的功能
好了,我们下节课再见!