Skip to content

Commit d163908

Browse files
committed
Add image manipulation examples
1 parent 5a8f0bf commit d163908

File tree

8 files changed

+517
-11
lines changed

8 files changed

+517
-11
lines changed

README.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,22 @@
2222

2323
### Metrics
2424
```text
25-
15074 number of properties
26-
10493 number of functions
27-
8939 number of classes
25+
15199 number of properties
26+
10523 number of functions
27+
8940 number of classes
2828
239 number of packages
29-
3529 number of kt files
29+
3531 number of kt files
3030
```
3131

3232

3333
### Complexity Report
3434
```text
35-
266156 lines of code (loc)
36-
165313 source lines of code (sloc)
37-
120749 logical lines of code (lloc)
38-
72537 comment lines of code (cloc)
39-
24952 cyclomatic complexity (mcc)
40-
20348 cognitive complexity
35+
266642 lines of code (loc)
36+
165721 source lines of code (sloc)
37+
121060 logical lines of code (lloc)
38+
72547 comment lines of code (cloc)
39+
25022 cyclomatic complexity (mcc)
40+
20410 cognitive complexity
4141
0 number of total code smells
4242
43 comment source ratio
4343
206 mcc per 1,000 lloc

api/Kotlin-Lab.api

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19732,6 +19732,27 @@ public abstract interface class dev/shtanko/concurrency/coroutines/examples/IAto
1973219732
public abstract fun transferFunds (Ldev/shtanko/concurrency/coroutines/examples/AtomicBankTransfer$Account;Ldev/shtanko/concurrency/coroutines/examples/AtomicBankTransfer$Account;DLkotlin/coroutines/Continuation;)Ljava/lang/Object;
1973319733
}
1973419734

19735+
public final class dev/shtanko/concurrency/coroutines/examples/ImageProcessingKt {
19736+
public static final fun adjustBrightness (Ljava/awt/image/BufferedImage;I)Ljava/awt/image/BufferedImage;
19737+
public static synthetic fun adjustBrightness$default (Ljava/awt/image/BufferedImage;IILjava/lang/Object;)Ljava/awt/image/BufferedImage;
19738+
public static final fun applySepia (Ljava/awt/image/BufferedImage;)Ljava/awt/image/BufferedImage;
19739+
public static final fun blur (Ljava/awt/image/BufferedImage;I)Ljava/awt/image/BufferedImage;
19740+
public static synthetic fun blur$default (Ljava/awt/image/BufferedImage;IILjava/lang/Object;)Ljava/awt/image/BufferedImage;
19741+
public static final fun decodeImage ([B)Ljava/awt/image/BufferedImage;
19742+
public static final fun display ([B)V
19743+
public static final fun flipVertical (Ljava/awt/image/BufferedImage;)Ljava/awt/image/BufferedImage;
19744+
public static final fun invertColors (Ljava/awt/image/BufferedImage;)Ljava/awt/image/BufferedImage;
19745+
public static final fun main ()V
19746+
public static synthetic fun main ([Ljava/lang/String;)V
19747+
public static final fun mirrorHorizontal (Ljava/awt/image/BufferedImage;)Ljava/awt/image/BufferedImage;
19748+
public static final fun rotate90 (Ljava/awt/image/BufferedImage;)Ljava/awt/image/BufferedImage;
19749+
public static final fun toBlackAndWhite (Ljava/awt/image/BufferedImage;I)Ljava/awt/image/BufferedImage;
19750+
public static synthetic fun toBlackAndWhite$default (Ljava/awt/image/BufferedImage;IILjava/lang/Object;)Ljava/awt/image/BufferedImage;
19751+
public static final fun toByteArray (Ljava/awt/image/BufferedImage;Ljava/lang/String;)[B
19752+
public static synthetic fun toByteArray$default (Ljava/awt/image/BufferedImage;Ljava/lang/String;ILjava/lang/Object;)[B
19753+
public static final fun toGrayscale (Ljava/awt/image/BufferedImage;)Ljava/awt/image/BufferedImage;
19754+
}
19755+
1973519756
public final class dev/shtanko/concurrency/coroutines/flow/EmployeeManager {
1973619757
public static final field INSTANCE Ldev/shtanko/concurrency/coroutines/flow/EmployeeManager;
1973719758
public final fun getEmployee (I)Lkotlinx/coroutines/flow/Flow;
@@ -24338,6 +24359,7 @@ public final class dev/shtanko/utils/Quintuple$Companion {
2433824359
}
2433924360

2434024361
public final class dev/shtanko/utils/ResourcesKt {
24362+
public static final fun readImageBytes (Ljava/lang/String;)[B
2434124363
public static final fun readJsonFromResource (Ljava/lang/String;)Ljava/lang/String;
2434224364
}
2434324365

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
package dev.shtanko.concurrency.coroutines.examples
2+
3+
import dev.shtanko.utils.ResourceFilePath
4+
import dev.shtanko.utils.readImageBytes
5+
import java.awt.Color
6+
import java.awt.image.BufferedImage
7+
import java.io.ByteArrayInputStream
8+
import javax.imageio.ImageIO
9+
import javax.swing.ImageIcon
10+
import javax.swing.JFrame
11+
import javax.swing.JLabel
12+
import kotlinx.coroutines.flow.flowOf
13+
import kotlinx.coroutines.flow.map
14+
import kotlinx.coroutines.runBlocking
15+
16+
private const val RED_WEIGHT = 0.299
17+
private const val GREEN_WEIGHT = 0.587
18+
private const val BLUE_WEIGHT = 0.114
19+
20+
private const val SEPIA_RED_SCALE_R = 0.393
21+
private const val SEPIA_RED_SCALE_G = 0.769
22+
private const val SEPIA_RED_SCALE_B = 0.189
23+
private const val SEPIA_GREEN_SCALE_R = 0.349
24+
private const val SEPIA_GREEN_SCALE_G = 0.686
25+
private const val SEPIA_GREEN_SCALE_B = 0.168
26+
private const val SEPIA_BLUE_SCALE_R = 0.272
27+
private const val SEPIA_BLUE_SCALE_G = 0.534
28+
private const val SEPIA_BLUE_SCALE_B = 0.131
29+
30+
private const val MAX_COLOR_VALUE = 255
31+
private const val BRIGHTNESS_ADJUSTMENT = 40
32+
33+
private const val DEFAULT_THRESHOLD = 128
34+
private const val RGB_COMPONENT_COUNT = 3
35+
private const val MIN_COLOR = 0
36+
private const val MAX_COLOR = 255
37+
38+
private const val KERNEL_SIZE = 3
39+
private const val KERNEL_CENTER = KERNEL_SIZE / 2
40+
41+
private val GAUSSIAN_KERNEL = arrayOf(
42+
doubleArrayOf(1.0 / 16, 2.0 / 16, 1.0 / 16),
43+
doubleArrayOf(2.0 / 16, 4.0 / 16, 2.0 / 16),
44+
doubleArrayOf(1.0 / 16, 2.0 / 16, 1.0 / 16)
45+
)
46+
47+
fun toGrayscale(image: BufferedImage): BufferedImage {
48+
val result = BufferedImage(image.width, image.height, BufferedImage.TYPE_INT_RGB)
49+
for (x in 0 until image.width) {
50+
for (y in 0 until image.height) {
51+
val rgb = Color(image.getRGB(x, y))
52+
val gray = (RED_WEIGHT * rgb.red + GREEN_WEIGHT * rgb.green + BLUE_WEIGHT * rgb.blue).toInt()
53+
val grayColor = Color(gray, gray, gray)
54+
result.setRGB(x, y, grayColor.rgb)
55+
}
56+
}
57+
return result
58+
}
59+
60+
fun applySepia(image: BufferedImage): BufferedImage {
61+
for (x in 0 until image.width) {
62+
for (y in 0 until image.height) {
63+
val color = Color(image.getRGB(x, y))
64+
65+
val r = color.red
66+
val g = color.green
67+
val b = color.blue
68+
69+
val tr = (SEPIA_RED_SCALE_R * r + SEPIA_RED_SCALE_G * g + SEPIA_RED_SCALE_B * b).toInt()
70+
.coerceAtMost(MAX_COLOR_VALUE)
71+
val tg = (SEPIA_GREEN_SCALE_R * r + SEPIA_GREEN_SCALE_G * g + SEPIA_GREEN_SCALE_B * b).toInt()
72+
.coerceAtMost(MAX_COLOR_VALUE)
73+
val tb = (SEPIA_BLUE_SCALE_R * r + SEPIA_BLUE_SCALE_G * g + SEPIA_BLUE_SCALE_B * b).toInt()
74+
.coerceAtMost(MAX_COLOR_VALUE)
75+
76+
val newColor = Color(tr, tg, tb)
77+
image.setRGB(x, y, newColor.rgb)
78+
}
79+
}
80+
return image
81+
}
82+
83+
fun invertColors(image: BufferedImage): BufferedImage {
84+
val result = BufferedImage(image.width, image.height, BufferedImage.TYPE_INT_RGB)
85+
for (x in 0 until image.width) {
86+
for (y in 0 until image.height) {
87+
val rgb = Color(image.getRGB(x, y))
88+
val inverted = Color(
89+
MAX_COLOR_VALUE - rgb.red,
90+
MAX_COLOR_VALUE - rgb.green,
91+
MAX_COLOR_VALUE - rgb.blue,
92+
)
93+
result.setRGB(x, y, inverted.rgb)
94+
}
95+
}
96+
return result
97+
}
98+
99+
fun adjustBrightness(image: BufferedImage, delta: Int = BRIGHTNESS_ADJUSTMENT): BufferedImage {
100+
for (x in 0 until image.width) {
101+
for (y in 0 until image.height) {
102+
val color = Color(image.getRGB(x, y))
103+
val r = (color.red + delta).coerceIn(0, MAX_COLOR_VALUE)
104+
val g = (color.green + delta).coerceIn(0, MAX_COLOR_VALUE)
105+
val b = (color.blue + delta).coerceIn(0, MAX_COLOR_VALUE)
106+
val newColor = Color(r, g, b)
107+
image.setRGB(x, y, newColor.rgb)
108+
}
109+
}
110+
return image
111+
}
112+
113+
fun toBlackAndWhite(
114+
image: BufferedImage,
115+
threshold: Int = DEFAULT_THRESHOLD,
116+
): BufferedImage {
117+
val result = BufferedImage(image.width, image.height, BufferedImage.TYPE_INT_RGB)
118+
for (x in 0 until image.width) {
119+
for (y in 0 until image.height) {
120+
val rgb = Color(image.getRGB(x, y))
121+
val averageColor = (rgb.red + rgb.green + rgb.blue) / RGB_COMPONENT_COUNT
122+
val bw = if (averageColor < threshold) MIN_COLOR else MAX_COLOR
123+
result.setRGB(x, y, Color(bw, bw, bw).rgb)
124+
}
125+
}
126+
return result
127+
}
128+
129+
fun mirrorHorizontal(image: BufferedImage): BufferedImage {
130+
val result = BufferedImage(image.width, image.height, BufferedImage.TYPE_INT_RGB)
131+
for (x in 0 until image.width) {
132+
for (y in 0 until image.height) {
133+
result.setRGB(image.width - x - 1, y, image.getRGB(x, y))
134+
}
135+
}
136+
return result
137+
}
138+
139+
fun flipVertical(image: BufferedImage): BufferedImage {
140+
val result = BufferedImage(image.width, image.height, BufferedImage.TYPE_INT_RGB)
141+
for (x in 0 until image.width) {
142+
for (y in 0 until image.height) {
143+
result.setRGB(x, image.height - y - 1, image.getRGB(x, y))
144+
}
145+
}
146+
return result
147+
}
148+
149+
fun rotate90(image: BufferedImage): BufferedImage {
150+
val result = BufferedImage(image.height, image.width, BufferedImage.TYPE_INT_RGB)
151+
for (x in 0 until image.width) {
152+
for (y in 0 until image.height) {
153+
result.setRGB(y, image.width - x - 1, image.getRGB(x, y))
154+
}
155+
}
156+
return result
157+
}
158+
159+
fun display(image: ByteArray) {
160+
val icon = ImageIcon(image)
161+
val label = JLabel(icon)
162+
val frame = JFrame("Processed Image")
163+
frame.defaultCloseOperation = JFrame.EXIT_ON_CLOSE
164+
frame.contentPane.add(label)
165+
frame.pack()
166+
frame.isVisible = true
167+
}
168+
169+
fun decodeImage(bytes: ByteArray): BufferedImage =
170+
ImageIO.read(ByteArrayInputStream(bytes)) ?: error("Invalid image")
171+
172+
fun BufferedImage.toByteArray(format: String = "png"): ByteArray {
173+
val out = java.io.ByteArrayOutputStream()
174+
ImageIO.write(this, format, out)
175+
return out.toByteArray()
176+
}
177+
178+
fun blur(image: BufferedImage, iterations: Int = 1): BufferedImage {
179+
var currentImage = image
180+
repeat(iterations) {
181+
currentImage = singleBlurPass(currentImage)
182+
}
183+
return currentImage
184+
}
185+
186+
private fun singleBlurPass(image: BufferedImage): BufferedImage {
187+
val width = image.width
188+
val height = image.height
189+
val result = BufferedImage(width, height, BufferedImage.TYPE_INT_RGB)
190+
191+
for (x in 0 until width) {
192+
for (y in 0 until height) {
193+
var rSum = 0.0
194+
var gSum = 0.0
195+
var bSum = 0.0
196+
197+
for (kx in 0 until KERNEL_SIZE) {
198+
for (ky in 0 until KERNEL_SIZE) {
199+
val px = (x + kx - KERNEL_CENTER).coerceIn(0, width - 1)
200+
val py = (y + ky - KERNEL_CENTER).coerceIn(0, height - 1)
201+
val color = Color(image.getRGB(px, py))
202+
val weight = GAUSSIAN_KERNEL[kx][ky]
203+
rSum += color.red * weight
204+
gSum += color.green * weight
205+
bSum += color.blue * weight
206+
}
207+
}
208+
209+
val r = rSum.toInt().coerceIn(0, MAX_COLOR_VALUE)
210+
val g = gSum.toInt().coerceIn(0, MAX_COLOR_VALUE)
211+
val b = bSum.toInt().coerceIn(0, MAX_COLOR_VALUE)
212+
result.setRGB(x, y, Color(r, g, b).rgb)
213+
}
214+
}
215+
return result
216+
}
217+
218+
fun main(): Unit = runBlocking {
219+
val path: ResourceFilePath = "images/bd.jpg"
220+
val imageBytes = path.readImageBytes()
221+
222+
flowOf(imageBytes)
223+
.map { decodeImage(it) }
224+
.map { toGrayscale(it) }
225+
.map { applySepia(it) }
226+
.map { adjustBrightness(it) }
227+
.map { mirrorHorizontal(it) }
228+
.map { rotate90(it) }
229+
.map { rotate90(it) }
230+
.map { rotate90(it) }
231+
.map { rotate90(it) }
232+
.map { flipVertical(it) }
233+
.map { flipVertical(it) }
234+
.map { toBlackAndWhite(it) }
235+
.map { invertColors(it) }
236+
.map { blur(it, iterations = 4) }
237+
.map { it.toByteArray() }
238+
.collect { display(it) }
239+
}

src/main/kotlin/dev/shtanko/utils/Resources.kt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,15 @@ package dev.shtanko.utils
1919
import java.io.InputStream
2020
import java.io.InputStreamReader
2121
import java.nio.charset.StandardCharsets
22+
import java.nio.file.Files
23+
import java.nio.file.Paths
24+
import kotlin.jvm.Throws
2225

2326
typealias ResourceFileName = String
27+
typealias ResourceFilePath = String
2428

2529
/**
26-
* Reads the content of a JSON file from the resources directory.
30+
* Reads the content of a JSON file from the resource directory.
2731
*
2832
* @return The content of the JSON file as a string, or null if the file is not found.
2933
*/
@@ -37,3 +41,13 @@ fun ResourceFileName.readJsonFromResource(): String? {
3741
null
3842
}
3943
}
44+
45+
@Throws(IllegalStateException::class)
46+
fun ResourceFilePath.readImageBytes(): ByteArray {
47+
val classLoader = Thread.currentThread().contextClassLoader
48+
val resource = classLoader.getResource(this)
49+
?: error("Image not found")
50+
51+
val path = Paths.get(resource.toURI())
52+
return Files.readAllBytes(path)
53+
}

src/main/resources/images/bd.jpg

371 KB
Loading

0 commit comments

Comments
 (0)