-
Notifications
You must be signed in to change notification settings - Fork 0
/
ImageResizer.kt
163 lines (143 loc) · 5.99 KB
/
ImageResizer.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package seamcarving
import utils.GRADIENT
import utils.Pixel
import utils.Utils.openImage
import utils.Utils.saveImage
import utils.Parameters
import utils.squared
import java.awt.Color
import java.awt.image.BufferedImage
import kotlin.math.sqrt
enum class REDUCTION {
HORIZONTAL_REDUCTION, VERTICAL_REDUCTION
}
class ImageResizer() {
private lateinit var lowestSeamsPath: MutableList<Pixel>
private lateinit var imageArray: MutableList<MutableList<Int>>
private lateinit var outputImage: BufferedImage
private lateinit var energies: MutableList<MutableList<Double>>
private lateinit var cumulativeEnergies: MutableList<MutableList<Double>>
private lateinit var currentReduction: REDUCTION
private var height = 0
private var width = 0
fun resizeImage(image: BufferedImage, reduction: REDUCTION, decrease: Int) {
currentReduction = reduction
val inputImage = if (currentReduction == REDUCTION.VERTICAL_REDUCTION) image else transpose(image)
height = inputImage.height
width = inputImage.width
outputImage = BufferedImage(width - decrease, height, BufferedImage.TYPE_INT_RGB)
imageArray = MutableList(height) { MutableList(width) { 0 } }
for (y in 0 until inputImage.height) {
for (x in 0 until inputImage.width) {
imageArray[y][x] = inputImage.getRGB(x, y)
}
}
lowestSeamsPath = mutableListOf()
repeat(decrease) {
calculateEnergies()
calculateCumulativeEnergies()
findLowestSeam()
removeLowestSeam()
}
}
private fun calculateEnergies() {
energies = MutableList(width) { MutableList(height) { 0.0 } }
for (x in 0 until width) {
for (y in 0 until height) {
val xDiff = calculatePixelEnergy(x, y, GRADIENT.X_GRADIENT)
val yDiff = calculatePixelEnergy(x, y, GRADIENT.Y_GRADIENT)
energies[x][y] = sqrt(((xDiff + yDiff).toDouble()))
}
}
}
private fun calculatePixelEnergy(x: Int, y: Int, gradient: GRADIENT): Int {
val prevPixel: Pixel
val nextPixel: Pixel
val currentPixel: Pixel
if (gradient == GRADIENT.X_GRADIENT) {
currentPixel = Pixel(if (x == 0) 1 else if (x == width - 1) width - 2 else x, y)
prevPixel = Pixel(currentPixel.x - 1, y)
nextPixel = Pixel(currentPixel.x + 1, y)
} else {
currentPixel = Pixel(x, if (y == 0) 1 else if (y == height - 1) height - 2 else y)
prevPixel = Pixel(x, currentPixel.y - 1)
nextPixel = Pixel(x, currentPixel.y + 1)
}
return getRgbDiff(prevPixel, nextPixel)
}
private fun getRgbDiff(prevPixel: Pixel, nextPixel: Pixel): Int {
val prevPixelColor = Color(imageArray[prevPixel.y][prevPixel.x])
val nextPixelColor = Color(imageArray[nextPixel.y][nextPixel.x])
val redDiff = prevPixelColor.red - nextPixelColor.red
val greenDiff = prevPixelColor.green - nextPixelColor.green
val blueDiff = prevPixelColor.blue - nextPixelColor.blue
return redDiff.squared() + greenDiff.squared() + blueDiff.squared()
}
private fun calculateCumulativeEnergies() {
cumulativeEnergies = MutableList(height) { MutableList(width) { 0.0 } }
for (y in 0 until height) {
for (x in 0 until width) {
if (y == 0) {
// The top row has nothing above it, so the energies are the same as the source image
cumulativeEnergies[y][x] = energies[x][y]
} else {
// For each pixel in the rest of the rows, the energy is its own energy plus the minimal of the three energies above.
cumulativeEnergies[y][x] = energies[x][y] + getNextPixel(x, y).energy
}
}
}
}
private fun findLowestSeam() {
val x = cumulativeEnergies[height - 1].indexOf(cumulativeEnergies[height - 1].min())
lowestSeamsPath = getPath(x, height - 1)
}
private fun removeLowestSeam() {
lowestSeamsPath.forEach { p ->
imageArray[p.y].removeAt(p.x)
}
width -= 1
}
private fun getPath(startX: Int, startY: Int): MutableList<Pixel> {
var currentPixel = Pixel(startX, startY)
val path = mutableListOf(currentPixel)
for (y in height - 1 downTo 1) {
currentPixel = getNextPixel(currentPixel.x, currentPixel.y)
path.add(currentPixel)
}
return path
}
private fun getNextPixel(x: Int, y: Int): Pixel {
val top = Pixel(x, y - 1, cumulativeEnergies[y - 1][x])
val topLeft = if (x > 0) Pixel(x - 1, y - 1, cumulativeEnergies[y - 1][x - 1]) else top
val topRight = if (x < width - 1) Pixel(x + 1, y - 1, cumulativeEnergies[y - 1][x + 1]) else top
return setOf(top, topLeft, topRight).minBy { it.energy }
}
fun getOutputImage(): BufferedImage {
for (x in 0 until width) {
for (y in 0 until height) {
outputImage.setRGB(x, y, imageArray[y][x])
}
}
return if (currentReduction == REDUCTION.VERTICAL_REDUCTION) outputImage else transpose(outputImage)
}
}
private fun transpose(image: BufferedImage): BufferedImage {
val transposedImage = BufferedImage(image.height, image.width, image.type)
for (x in 0 until image.width) {
for (y in 0 until image.height) {
transposedImage.setRGB(y, x, image.getRGB(x, y))
}
}
return transposedImage
}
fun main(args: Array<String>) {
val params = Parameters(args[1], args[3], args[5].toInt(), args.last().toInt())
var image = openImage(params.inputPath)
ImageResizer().apply {
resizeImage(image, REDUCTION.VERTICAL_REDUCTION, params.width)
image = getOutputImage()
resizeImage(image, REDUCTION.HORIZONTAL_REDUCTION, params.height)
image = getOutputImage()
}
saveImage(params.outputPath, image)
}