-
Notifications
You must be signed in to change notification settings - Fork 30
/
UIImage.kt
178 lines (152 loc) · 6.26 KB
/
UIImage.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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
package gg.essential.elementa.components
import gg.essential.elementa.UIComponent
import gg.essential.elementa.components.image.*
import gg.essential.elementa.utils.ResourceCache
import gg.essential.elementa.utils.drawTexture
import gg.essential.universal.UGraphics
import gg.essential.universal.UMatrixStack
import gg.essential.universal.utils.ReleasedDynamicTexture
import org.lwjgl.opengl.GL11
import java.awt.Color
import java.awt.image.BufferedImage
import java.io.File
import java.net.HttpURLConnection
import java.net.URL
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ConcurrentLinkedQueue
import javax.imageio.ImageIO
/**
* Component for drawing arbitrary images from [BufferedImage].
*
* There are companion functions available to get [UIImage]s from other sources,
* such as URLs: [Companion.ofURL], [Companion.ofFile] and [Companion.ofResource].
*/
open class UIImage @JvmOverloads constructor(
private val imageFuture: CompletableFuture<BufferedImage>,
private val loadingImage: ImageProvider = DefaultLoadingImage,
private val failureImage: ImageProvider = DefaultFailureImage,
) : UIComponent(), ImageProvider, CacheableImage {
private var texture: ReleasedDynamicTexture? = null
private val waiting = ConcurrentLinkedQueue<CacheableImage>()
var imageWidth = 1f
var imageHeight = 1f
var destroy = true
val isLoaded: Boolean
get() = texture != null
var textureMinFilter = TextureScalingMode.NEAREST
var textureMagFilter = TextureScalingMode.NEAREST
init {
imageFuture.exceptionally {
it.printStackTrace()
return@exceptionally null
}.thenAcceptAsync {
if (it == null) {
destroy = false
return@thenAcceptAsync
}
imageWidth = it.width.toFloat()
imageHeight = it.height.toFloat()
imageFuture.obtrudeValue(null)
// In versions before 1.15, we make the bufferedImage.getRGB call without the upload in the
// constructor since that takes most of the CPU time and we upload the actual texture during the
// first call to uploadTexture or getGlTextureId
// Same for 1.15+ actually, except that it is not getRGB but serialization to byte[] (so we can re-parse it
// as a NativeImage) which is slow.
val texture = UGraphics.getTexture(it)
Window.enqueueRenderOperation {
texture?.uploadTexture()
this.texture = texture
while (waiting.isEmpty().not())
waiting.poll().applyTexture(texture)
}
}
}
@Deprecated(
"Please provide a completable future instead",
ReplaceWith("CompletableFuture.supplyAsync(imageFunction)", "java.util.concurrent.CompletableFuture"),
level = DeprecationLevel.ERROR
)
constructor(imageFunction: () -> BufferedImage) : this(CompletableFuture.supplyAsync(imageFunction))
override fun drawImage(matrixStack: UMatrixStack, x: Double, y: Double, width: Double, height: Double, color: Color) {
when {
texture != null -> drawTexture(matrixStack, texture!!, color, x, y, width, height, textureMinFilter.glMode, textureMagFilter.glMode)
imageFuture.isCompletedExceptionally -> failureImage.drawImageCompat(matrixStack, x, y, width, height, color)
else -> loadingImage.drawImageCompat(matrixStack, x, y, width, height, color)
}
}
override fun draw(matrixStack: UMatrixStack) {
beforeDrawCompat(matrixStack)
val x = this.getLeft().toDouble()
val y = this.getTop().toDouble()
val width = this.getWidth().toDouble()
val height = this.getHeight().toDouble()
val color = this.getColor()
if (color.alpha == 0) {
return super.draw(matrixStack)
}
drawImage(matrixStack, x, y, width, height, color)
super.draw(matrixStack)
}
override fun supply(image: CacheableImage) {
if (texture != null) {
image.applyTexture(texture)
return
}
waiting.add(image)
}
override fun applyTexture(texture: ReleasedDynamicTexture?) {
this.texture = texture
while (waiting.isEmpty().not())
waiting.poll().applyTexture(texture)
}
enum class TextureScalingMode(internal val glMode: Int) {
NEAREST(GL11.GL_NEAREST),
LINEAR(GL11.GL_LINEAR),
NEAREST_MIPMAP_NEAREST(GL11.GL_NEAREST_MIPMAP_NEAREST),
LINEAR_MIPMAP_NEAREST(GL11.GL_LINEAR_MIPMAP_NEAREST),
NEAREST_MIPMAP_LINEAR(GL11.GL_NEAREST_MIPMAP_LINEAR),
LINEAR_MIPMAP_LINEAR(GL11.GL_LINEAR_MIPMAP_LINEAR)
}
companion object {
val defaultResourceCache = ResourceCache(50)
@JvmStatic
fun ofFile(file: File): UIImage {
return UIImage(CompletableFuture.supplyAsync { ImageIO.read(file) })
}
@JvmStatic
fun ofURL(url: URL): UIImage {
return UIImage(CompletableFuture.supplyAsync { get(url) })
}
@JvmStatic
fun ofURL(url: URL, cache: ImageCache): UIImage {
return UIImage(CompletableFuture.supplyAsync {
return@supplyAsync cache[url] ?: get(url).also {
cache[url] = it
}
})
}
@JvmStatic
fun ofResource(path: String): UIImage {
return UIImage(CompletableFuture.supplyAsync {
ImageIO.read(this::class.java.getResourceAsStream(path))
})
}
@JvmStatic
fun ofResourceCached(path: String): UIImage {
return ofResourceCached(path, defaultResourceCache)
}
@JvmStatic
fun ofResourceCached(path: String, resourceCache: ResourceCache): UIImage {
return resourceCache.getUIImage(path) as UIImage
}
@JvmStatic
fun get(url: URL): BufferedImage {
val connection = url.openConnection() as HttpURLConnection
connection.requestMethod = "GET"
connection.useCaches = true
connection.addRequestProperty("User-Agent", "Mozilla/4.76 (Elementa)")
connection.doOutput = true
return ImageIO.read(connection.inputStream)
}
}
}