|
2 | 2 |
|
3 | 3 | package de.berlindroid.zekompanion.server
|
4 | 4 |
|
5 |
| -import de.berlindroid.zekompanion.BadgePayload |
6 |
| -import de.berlindroid.zekompanion.base64 |
7 |
| -import de.berlindroid.zekompanion.debase64 |
8 |
| -import de.berlindroid.zekompanion.ditherFloydSteinberg |
9 |
| -import de.berlindroid.zekompanion.grayscale |
10 |
| -import de.berlindroid.zekompanion.invert |
11 |
| -import de.berlindroid.zekompanion.resize |
12 |
| -import de.berlindroid.zekompanion.server.Operation.FloydSteinberg |
13 |
| -import de.berlindroid.zekompanion.server.Operation.Grayscale |
14 |
| -import de.berlindroid.zekompanion.server.Operation.Invert |
15 |
| -import de.berlindroid.zekompanion.server.Operation.Resize |
16 |
| -import de.berlindroid.zekompanion.server.Operation.Threshold |
17 |
| -import de.berlindroid.zekompanion.threshold |
18 |
| -import de.berlindroid.zekompanion.toBinary |
19 |
| -import de.berlindroid.zekompanion.zipit |
20 |
| -import io.ktor.http.HttpStatusCode |
| 5 | +import de.berlindroid.zekompanion.server.routers.imageBin |
| 6 | +import de.berlindroid.zekompanion.server.routers.imagePng |
| 7 | +import de.berlindroid.zekompanion.server.routers.index |
21 | 8 | import io.ktor.serialization.kotlinx.json.json
|
22 |
| -import io.ktor.server.application.call |
23 | 9 | import io.ktor.server.application.install
|
24 | 10 | import io.ktor.server.engine.embeddedServer
|
25 |
| -import io.ktor.server.http.content.CompressedFileType |
26 | 11 | import io.ktor.server.http.content.staticResources
|
27 | 12 | import io.ktor.server.netty.Netty
|
28 | 13 | import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
|
29 |
| -import io.ktor.server.request.receiveNullable |
30 |
| -import io.ktor.server.response.respondText |
31 |
| -import io.ktor.server.routing.post |
32 | 14 | import io.ktor.server.routing.routing
|
33 |
| -import io.ktor.util.encodeBase64 |
34 |
| -import kotlinx.serialization.SerialName |
35 |
| -import kotlinx.serialization.Serializable |
36 |
| -import java.awt.image.BufferedImage |
37 |
| -import java.io.ByteArrayInputStream |
38 |
| -import java.io.ByteArrayOutputStream |
39 |
| -import java.nio.ByteBuffer |
40 |
| -import java.nio.IntBuffer |
41 |
| -import javax.imageio.ImageIO |
42 | 15 |
|
43 |
| -@Serializable |
44 |
| -sealed class Operation { |
45 |
| - @Serializable |
46 |
| - @SerialName("FloydSteinberg") |
47 |
| - data class FloydSteinberg(val width: Int, val height: Int) : Operation() |
| 16 | +private const val DEFAULT_PORT = 8000 |
48 | 17 |
|
49 |
| - @Serializable |
50 |
| - @SerialName("Resize") |
51 |
| - data class Resize(val width: Int, val height: Int) : Operation() |
52 |
| - |
53 |
| - @Serializable |
54 |
| - @SerialName("Threshold") |
55 |
| - data class Threshold(val threshold: Int) : Operation() |
56 |
| - |
57 |
| - @Serializable |
58 |
| - @SerialName("Invert") |
59 |
| - data object Invert : Operation() |
60 |
| - |
61 |
| - @Serializable |
62 |
| - @SerialName("Grayscale") |
63 |
| - data object Grayscale : Operation() |
64 |
| -} |
65 |
| - |
66 |
| -@Serializable |
67 |
| -data class ImageRequest( |
68 |
| - val operations: List<Operation> = emptyList(), |
69 |
| - val image: String = "", |
70 |
| - val width: Int = -1, |
71 |
| - val height: Int = -1, |
72 |
| -) |
73 |
| - |
74 |
| -fun main(args:Array<String>) { |
75 |
| - val port = if (args.isNotEmpty()){ |
| 18 | +fun main(args: Array<String>) { |
| 19 | + val port = if (args.isNotEmpty()) { |
76 | 20 | args.first().toInt()
|
77 | 21 | } else {
|
78 |
| - 8000 |
| 22 | + DEFAULT_PORT |
79 | 23 | }
|
80 | 24 | println("🪪Serving on port $port.")
|
81 | 25 |
|
82 | 26 | embeddedServer(Netty, port = port) {
|
83 | 27 | install(ContentNegotiation) {
|
84 | 28 | json()
|
85 | 29 | }
|
86 |
| - |
87 | 30 | routing {
|
88 | 31 | staticResources("/", "static") {
|
89 |
| - default("static/index.html") |
90 |
| - preCompressed(CompressedFileType.GZIP) |
| 32 | + index() |
91 | 33 | }
|
92 | 34 |
|
93 |
| - post("/api/image/bin") { |
94 |
| - try { |
95 |
| - val image = call.receiveNullable<ImageRequest>() ?: throw IllegalArgumentException("Payload is null") |
96 |
| - |
97 |
| - val payload = BadgePayload( |
98 |
| - debug = false, |
99 |
| - type = "preview", |
100 |
| - meta = "", |
101 |
| - payload = image.transform() |
102 |
| - .toBinary() |
103 |
| - .zipit() |
104 |
| - .base64(), |
105 |
| - ) |
106 |
| - |
107 |
| - call.respondText(payload.toBadgeCommand()) |
108 |
| - } catch (e: Exception) { |
109 |
| - e.printStackTrace() |
110 |
| - call.respondText("Error: ${e.message}") |
111 |
| - } |
112 |
| - } |
113 |
| - |
114 |
| - post("/api/image/png") { |
115 |
| - try { |
116 |
| - val payload = call.receiveNullable<ImageRequest>() ?: throw IllegalArgumentException("Payload is null") |
117 |
| - |
118 |
| - val image = payload.transform().toImage(payload.width, payload.height) |
119 |
| - |
120 |
| - val stream = ByteArrayOutputStream() |
121 |
| - ImageIO.write(image, "png", stream) |
122 |
| - |
123 |
| - call.respondText(stream.toByteArray().encodeBase64()) |
124 |
| - } catch (e: Exception) { |
125 |
| - e.printStackTrace() |
126 |
| - call.respondText("Error: ${e.message}", status = HttpStatusCode.MethodNotAllowed) |
127 |
| - } |
128 |
| - |
129 |
| - } |
| 35 | + imageBin() |
| 36 | + imagePng() |
130 | 37 | }
|
131 | 38 | }.start(wait = true)
|
132 | 39 | }
|
133 |
| - |
134 |
| -private fun IntBuffer.toImage(width: Int, height: Int): BufferedImage { |
135 |
| - val output = BufferedImage(width, height, BufferedImage.TYPE_INT_RGB) |
136 |
| - output.setRGB(0, 0, width, height, array(), 0, width) |
137 |
| - return output |
138 |
| -} |
139 |
| - |
140 |
| -private fun ImageRequest.transform(): IntBuffer { |
141 |
| - var image = image.debase64().toImage().toPixels() |
142 |
| - |
143 |
| - operations.forEach { operation -> |
144 |
| - image = when (operation) { |
145 |
| - is FloydSteinberg -> image.ditherFloydSteinberg(operation.width, operation.height) |
146 |
| - is Resize -> image.resize(width, height, operation.width, operation.height) |
147 |
| - is Threshold -> image.threshold(operation.threshold) |
148 |
| - is Invert -> image.invert() |
149 |
| - is Grayscale -> image.grayscale() |
150 |
| - } |
151 |
| - } |
152 |
| - return image |
153 |
| -} |
154 |
| - |
155 |
| -private fun BufferedImage.toPixels(): IntBuffer { |
156 |
| - val output = IntBuffer.allocate(width * height) |
157 |
| - getRGB(0, 0, width, height, output.array(), 0, width) |
158 |
| - return output |
159 |
| -} |
160 |
| - |
161 |
| -private fun ByteBuffer.toImage(): BufferedImage { |
162 |
| - val stream = ByteArrayInputStream(array()) |
163 |
| - val image = ImageIO.read(stream) |
164 |
| - return image |
165 |
| -} |
0 commit comments