/
ColorBuffer.kt
242 lines (199 loc) · 7.69 KB
/
ColorBuffer.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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
@file:JvmName("ColorBufferJVM")
package org.openrndr.draw
import kotlinx.coroutines.runBlocking
import org.openrndr.color.ColorRGBa
import org.openrndr.internal.Driver
import org.openrndr.internal.ImageDriver
import org.openrndr.math.Vector2
import org.openrndr.shape.IntRectangle
import org.openrndr.shape.Rectangle
import org.openrndr.utils.buffer.MPPBuffer
import java.io.File
import java.net.URL
import java.nio.ByteBuffer
/**
* representation for simple images stored on GPU memory
*
* [ColorBuffer] is an unmanaged GPU resource, the user is responsible for destroying a [ColorBuffer] once it is no
* longer used.
*/
actual abstract class ColorBuffer {
actual abstract val session: Session?
/** the width of the [ColorBuffer] in device units */
actual abstract val width: Int
/** the height of the [ColorBuffer] in device units */
actual abstract val height: Int
/** the content scale of the [ColorBuffer] */
actual abstract val contentScale: Double
/**
* the [ColorFormat] of the image stored in the [ColorBuffer]
*/
actual abstract val format: ColorFormat
/**
* the [ColorType] of the image stored in the [ColorBuffer]
*/
actual abstract val type: ColorType
/** the number of mipmap levels */
actual abstract val levels: Int
/** the multisampling method used for this [ColorBuffer] */
actual abstract val multisample: BufferMultisample
actual val bounds: Rectangle get() = Rectangle(Vector2.ZERO, width * 1.0, height * 1.0)
actual val effectiveWidth: Int get() = (width * contentScale).toInt()
actual val effectiveHeight: Int get() = (height * contentScale).toInt()
/** return the buffer size in bytes */
fun bufferSize(level: Int = 0): Long {
val baseSize = ((effectiveWidth * effectiveHeight) shr level).toLong()
return when (type) {
ColorType.DXT1 -> (baseSize) / 2
ColorType.DXT3, ColorType.DXT5 -> baseSize
ColorType.BPTC_FLOAT -> TODO()
ColorType.BPTC_UFLOAT -> TODO()
ColorType.BPTC_UNORM -> TODO()
else -> baseSize * format.componentCount * type.componentSize
}
}
/** save the [ColorBuffer] to [File] */
abstract fun saveToFile(
file: File,
imageFileFormat: ImageFileFormat = ImageFileFormat.guessFromExtension(file.extension) ?: ImageFileFormat.PNG,
async: Boolean = true
)
/** return a base64 data url representation */
abstract fun toDataUrl(imageFileFormat: ImageFileFormat = ImageFileFormat.JPG): String
abstract fun write(
sourceBuffer: ByteBuffer,
sourceFormat: ColorFormat = format,
sourceType: ColorType = type,
level: Int = 0
)
/**
* read the contents of the [ColorBuffer] and write to [targetBuffer], potentially with format and type conversions
* @param targetBuffer a [ByteBuffer] to which the contents of the [ColorBuffer] will be written
* @param targetFormat the [ColorFormat] that is used for the image data stored in [targetBuffer], default is [ColorBuffer.format]
* @param targetType the [ColorType] that is used for the image data stored in [targetBuffer], default is [ColorBuffer.type]
* @param level the mipmap-level of [ColorBuffer] to read from
*/
abstract fun read(
targetBuffer: ByteBuffer,
targetFormat: ColorFormat = format,
targetType: ColorType = type,
level: Int = 0
)
/**
* create a cropped copy of the [ColorBuffer]
* @param sourceRectangle
*/
fun crop(sourceRectangle: IntRectangle): ColorBuffer {
val cropped = createEquivalent(width = sourceRectangle.width, height = sourceRectangle.height)
copyTo(
cropped,
0, 0,
sourceRectangle, IntRectangle(0, 0, sourceRectangle.width, sourceRectangle.height), MagnifyingFilter.NEAREST
)
return cropped
}
/**
* copies contents to a target color buffer
* @param target the color buffer to which contents will be copied
* @param fromLevel the mip-map level from which will be copied
* @param toLevel the mip-map level of [target] to which will be copied
* @param sourceRectangle rectangle in pixel units that specifies where to read from
* @param targetRectangle rectangle in pixel units that specifies where to write to
* @param filter filter to use for copying
*/
actual abstract fun copyTo(
target: ColorBuffer,
fromLevel: Int,
toLevel: Int,
sourceRectangle: IntRectangle,
targetRectangle: IntRectangle,
filter: MagnifyingFilter
)
actual abstract fun copyTo(
target: ColorBuffer,
fromLevel: Int,
toLevel: Int,
filter: MagnifyingFilter
)
actual abstract fun copyTo(target: ArrayTexture, layer: Int, fromLevel: Int, toLevel: Int)
actual abstract fun fill(color: ColorRGBa)
/** the wrapping mode to use in the horizontal direction */
actual abstract var wrapU: WrapMode
/** the wrapping mode to use in the vertical direction */
actual abstract var wrapV: WrapMode
/** the filter to use when displaying at sizes smaller than the original */
abstract var filterMin: MinifyingFilter
/** the filter to use when display at sizes larger than the original */
abstract var filterMag: MagnifyingFilter
abstract val shadow: ColorBufferShadow
/**
* sets the [ColorBuffer] filter for minifying and magnification
*/
actual abstract fun filter(filterMin: MinifyingFilter, filterMag: MagnifyingFilter)
companion object {
}
/** permanently destroy the underlying [ColorBuffer] resources, [ColorBuffer] can not be used after it is destroyed */
actual abstract fun destroy()
/** bind the colorbuffer to a texture unit, internal API */
actual abstract fun bind(unit: Int)
/** generates mipmaps from the top-level mipmap */
actual abstract fun generateMipmaps()
/** the (unitless?) degree of anisotropy to be used in filtering */
actual abstract var anisotropy: Double
/**
* should the v coordinate be flipped because the [ColorBuffer] contents are stored upside-down?
*/
actual abstract var flipV: Boolean
actual abstract fun write(
sourceBuffer: MPPBuffer,
sourceFormat: ColorFormat,
sourceType: ColorType,
x: Int,
y: Int,
width: Int,
height: Int,
level: Int
)
}
/**
* load an image from a file or url encoded as [String], also accepts base64 encoded data urls
*/
actual fun loadImage(fileOrUrl: String, formatHint: ImageFileFormat?, session: Session?): ColorBuffer {
val data = ImageDriver.instance.loadImage(fileOrUrl, formatHint)
return try {
val cb = colorBuffer(data.width, data.height, 1.0, data.format, data.type, session = session)
cb.write(data.data?.byteBuffer ?: error("no data"))
cb
} finally {
data.close()
}
}
/**
* load an image from [File]
*/
fun loadImage(
file: File,
formatHint: ImageFileFormat? = ImageFileFormat.guessFromExtension(file.extension),
session: Session? = Session.active
): ColorBuffer {
return loadImage(file.absolutePath, formatHint, session)
}
/**
* load an image from an [url]
*/
fun loadImage(
url: URL,
formatHint: ImageFileFormat? = ImageFileFormat.guessFromExtension(url.toExternalForm().split(".").lastOrNull()),
session: Session? = Session.active
): ColorBuffer {
return loadImage(url.toExternalForm(), formatHint, session)
}
actual suspend fun loadImageSuspend(
fileOrUrl: String,
formatHint: ImageFileFormat?,
session: Session?
): ColorBuffer {
return runBlocking {
loadImage(fileOrUrl, formatHint, session)
}
}