Skip to content

Commit fe4b3c5

Browse files
committed
Refactor image encoding in dataframe serialization
* Refactored image encoding logic for DataFrame serialization. * Removed custom renderer for buffered images as it affects rendering of single image objects
1 parent c2c4251 commit fe4b3c5

File tree

8 files changed

+44
-78
lines changed

8 files changed

+44
-78
lines changed

core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/io/writeJson.kt

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import org.jetbrains.kotlinx.dataframe.impl.io.SerializationKeys.METADATA
2323
import org.jetbrains.kotlinx.dataframe.impl.io.SerializationKeys.NCOL
2424
import org.jetbrains.kotlinx.dataframe.impl.io.SerializationKeys.NROW
2525
import org.jetbrains.kotlinx.dataframe.impl.io.SerializationKeys.VERSION
26-
import org.jetbrains.kotlinx.dataframe.io.ImageEncodingOptions
26+
import org.jetbrains.kotlinx.dataframe.io.Base64ImageEncodingOptions
2727
import org.jetbrains.kotlinx.dataframe.io.arrayColumnName
2828
import org.jetbrains.kotlinx.dataframe.io.valueColumnName
2929
import org.jetbrains.kotlinx.dataframe.ncol
@@ -61,7 +61,7 @@ internal fun KlaxonJson.encodeRowWithMetadata(
6161
frame: ColumnsContainer<*>,
6262
index: Int,
6363
rowLimit: Int? = null,
64-
imageEncodingOptions: ImageEncodingOptions = ImageEncodingOptions()
64+
imageEncodingOptions: Base64ImageEncodingOptions? = null
6565
): JsonObject? {
6666
val values = frame.columns().map { col ->
6767
when (col) {
@@ -96,7 +96,7 @@ private val valueTypes =
9696
internal fun KlaxonJson.encodeValue(
9797
col: AnyCol,
9898
index: Int,
99-
imageEncodingOptions: ImageEncodingOptions = ImageEncodingOptions(encodeAsBase64 = false)
99+
imageEncodingOptions: Base64ImageEncodingOptions? = null
100100
): Any? = when {
101101
col.isList() -> col[index]?.let { list ->
102102
val values = (list as List<*>).map {
@@ -109,28 +109,26 @@ internal fun KlaxonJson.encodeValue(
109109
}
110110
array(values)
111111
} ?: array()
112+
112113
col.typeClass in valueTypes -> {
113114
val v = col[index]
114115
if ((v is Double && v.isNaN()) || (v is Float && v.isNaN())) {
115116
v.toString()
116117
} else v
117118
}
118119

119-
col.typeClass == BufferedImage::class -> col[index]?.let { image ->
120-
encodeBufferedImage(image as BufferedImage, imageEncodingOptions)
121-
} ?: ""
120+
col.typeClass == BufferedImage::class && imageEncodingOptions != null ->
121+
col[index]?.let { image ->
122+
encodeBufferedImageAsBase64(image as BufferedImage, imageEncodingOptions)
123+
} ?: ""
122124

123125
else -> col[index]?.toString()
124126
}
125127

126-
private fun encodeBufferedImage(
128+
private fun encodeBufferedImageAsBase64(
127129
image: BufferedImage,
128-
imageEncodingOptions: ImageEncodingOptions = ImageEncodingOptions()
130+
imageEncodingOptions: Base64ImageEncodingOptions = Base64ImageEncodingOptions()
129131
): String? {
130-
if (!imageEncodingOptions.encodeAsBase64) {
131-
return image.toString()
132-
}
133-
134132
return try {
135133
val preparedImage = if (imageEncodingOptions.isLimitSizeOn) {
136134
image.resizeKeepingAspectRatio(imageEncodingOptions.imageSizeLimit)
@@ -153,7 +151,7 @@ private fun encodeBufferedImage(
153151
internal fun KlaxonJson.encodeFrameWithMetadata(
154152
frame: AnyFrame,
155153
rowLimit: Int? = null,
156-
imageEncodingOptions: ImageEncodingOptions = ImageEncodingOptions()
154+
imageEncodingOptions: Base64ImageEncodingOptions? = null
157155
): JsonArray<*> {
158156
val valueColumn = frame.extractValueColumn()
159157
val arrayColumn = frame.extractArrayColumn()
@@ -253,7 +251,7 @@ internal fun KlaxonJson.encodeDataFrameWithMetadata(
253251
frame: AnyFrame,
254252
rowLimit: Int,
255253
nestedRowLimit: Int? = null,
256-
imageEncodingOptions: ImageEncodingOptions = ImageEncodingOptions()
254+
imageEncodingOptions: Base64ImageEncodingOptions? = null
257255
): JsonObject {
258256
return obj(
259257
VERSION to SERIALIZATION_VERSION,

core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/json.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ public fun AnyFrame.toJson(prettyPrint: Boolean = false, canonical: Boolean = fa
276276
* Applied for each frame column recursively
277277
* @param prettyPrint Specifies whether the output JSON should be formatted with indentation and line breaks.
278278
* @param canonical Specifies whether the output JSON should be in a canonical form.
279-
* @param imageEncodingOptions The options for encoding images in the DataFrame. Defaults to encode images as Base64.
279+
* @param imageEncodingOptions The options for encoding images. The default is null, which indicates that the image is not encoded as Base64.
280280
*
281281
* @return The DataFrame converted to a JSON string with metadata.
282282
*/
@@ -285,7 +285,7 @@ public fun AnyFrame.toJsonWithMetadata(
285285
nestedRowLimit: Int? = null,
286286
prettyPrint: Boolean = false,
287287
canonical: Boolean = false,
288-
imageEncodingOptions: ImageEncodingOptions = ImageEncodingOptions(encodeAsBase64 = true)
288+
imageEncodingOptions: Base64ImageEncodingOptions? = null
289289
): String {
290290
return json {
291291
encodeDataFrameWithMetadata(this@toJsonWithMetadata, rowLimit, nestedRowLimit, imageEncodingOptions)
@@ -297,12 +297,10 @@ internal const val DEFAULT_IMG_SIZE = 600
297297
/**
298298
* Class representing the options for encoding images.
299299
*
300-
* @property encodeAsBase64 Specifies whether the images should be encoded as Base64. Defaults to false.
301300
* @property imageSizeLimit The maximum size to which images should be resized. Defaults to the value of DEFAULT_IMG_SIZE.
302301
* @property options Bitwise-OR of the [GZIP_ON] and [LIMIT_SIZE_ON] constants. Defaults to [GZIP_ON] or [LIMIT_SIZE_ON].
303302
*/
304-
public class ImageEncodingOptions(
305-
public val encodeAsBase64: Boolean = false,
303+
public class Base64ImageEncodingOptions(
306304
public val imageSizeLimit: Int = DEFAULT_IMG_SIZE,
307305
private val options: Int = GZIP_ON or LIMIT_SIZE_ON
308306
) {
@@ -313,6 +311,7 @@ public class ImageEncodingOptions(
313311
get() = options and LIMIT_SIZE_ON == LIMIT_SIZE_ON
314312

315313
public companion object {
314+
public const val ALL_OFF: Int = 0
316315
public const val GZIP_ON: Int = 1 // 2^0
317316
public const val LIMIT_SIZE_ON: Int = 2 // 2^1
318317
}

core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/jupyter/Integration.kt

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,12 @@ import org.jetbrains.kotlinx.dataframe.dataTypes.IMG
4141
import org.jetbrains.kotlinx.dataframe.impl.codeGen.CodeGenerationReadResult
4242
import org.jetbrains.kotlinx.dataframe.impl.codeGen.urlCodeGenReader
4343
import org.jetbrains.kotlinx.dataframe.impl.createStarProjectedType
44-
import org.jetbrains.kotlinx.dataframe.impl.io.resizeKeepingAspectRatio
45-
import org.jetbrains.kotlinx.dataframe.impl.io.toBase64
46-
import org.jetbrains.kotlinx.dataframe.impl.io.toByteArray
4744
import org.jetbrains.kotlinx.dataframe.impl.renderType
4845
import org.jetbrains.kotlinx.dataframe.io.DataFrameHtmlData
4946
import org.jetbrains.kotlinx.dataframe.io.SupportedCodeGenerationFormat
5047
import org.jetbrains.kotlinx.dataframe.io.supportedFormats
5148
import org.jetbrains.kotlinx.jupyter.api.*
5249
import org.jetbrains.kotlinx.jupyter.api.libraries.*
53-
import java.awt.image.BufferedImage
5450
import kotlin.reflect.KClass
5551
import kotlin.reflect.KProperty
5652
import kotlin.reflect.KType
@@ -59,8 +55,6 @@ import kotlin.reflect.full.isSubtypeOf
5955
/** Users will get an error if their Kotlin Jupyter kernel is older than this version. */
6056
private const val MIN_KERNEL_VERSION = "0.11.0.198"
6157

62-
private const val DEFAULT_HTML_IMG_SIZE = 100
63-
6458
internal val newDataSchemas = mutableListOf<KClass<*>>()
6559

6660
internal class Integration(
@@ -209,19 +203,6 @@ internal class Integration(
209203
}
210204
}
211205

212-
notebook.renderersProcessor.registerWithoutOptimizing(
213-
createRenderer<BufferedImage> {
214-
val src = buildString {
215-
append("""data:image/$DEFAULT_HTML_IMG_SIZE;base64,""")
216-
append(
217-
it.resizeKeepingAspectRatio(DEFAULT_HTML_IMG_SIZE).toByteArray().toBase64()
218-
)
219-
}
220-
HTML("""<img src="$src"/>""")
221-
},
222-
ProcessingPriority.HIGHER
223-
)
224-
225206
with(JupyterHtmlRenderer(config.display, this)) {
226207
render<DisableRowsLimitWrapper>(
227208
{ "DataRow: index = ${it.value.rowsCount()}, columnsCount = ${it.value.columnsCount()}" },

core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/jupyter/JupyterHtmlRenderer.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.jetbrains.kotlinx.dataframe.jupyter
33
import com.beust.klaxon.json
44
import org.jetbrains.kotlinx.dataframe.api.take
55
import org.jetbrains.kotlinx.dataframe.impl.io.encodeFrame
6+
import org.jetbrains.kotlinx.dataframe.io.Base64ImageEncodingOptions
67
import org.jetbrains.kotlinx.dataframe.io.DataFrameHtmlData
78
import org.jetbrains.kotlinx.dataframe.io.DisplayConfiguration
89
import org.jetbrains.kotlinx.dataframe.io.toHTML
@@ -74,7 +75,11 @@ internal inline fun <reified T : Any> JupyterHtmlRenderer.render(
7475
)
7576
}.toJsonString()
7677
} else {
77-
df.toJsonWithMetadata(limit, reifiedDisplayConfiguration.rowsLimit)
78+
df.toJsonWithMetadata(
79+
limit,
80+
reifiedDisplayConfiguration.rowsLimit,
81+
imageEncodingOptions = Base64ImageEncodingOptions()
82+
)
7883
}
7984
notebook.renderAsIFrameAsNeeded(html, staticHtml, jsonEncodedDf)
8085
} else {

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/io/writeJson.kt

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import org.jetbrains.kotlinx.dataframe.impl.io.SerializationKeys.METADATA
2323
import org.jetbrains.kotlinx.dataframe.impl.io.SerializationKeys.NCOL
2424
import org.jetbrains.kotlinx.dataframe.impl.io.SerializationKeys.NROW
2525
import org.jetbrains.kotlinx.dataframe.impl.io.SerializationKeys.VERSION
26-
import org.jetbrains.kotlinx.dataframe.io.ImageEncodingOptions
26+
import org.jetbrains.kotlinx.dataframe.io.Base64ImageEncodingOptions
2727
import org.jetbrains.kotlinx.dataframe.io.arrayColumnName
2828
import org.jetbrains.kotlinx.dataframe.io.valueColumnName
2929
import org.jetbrains.kotlinx.dataframe.ncol
@@ -61,7 +61,7 @@ internal fun KlaxonJson.encodeRowWithMetadata(
6161
frame: ColumnsContainer<*>,
6262
index: Int,
6363
rowLimit: Int? = null,
64-
imageEncodingOptions: ImageEncodingOptions = ImageEncodingOptions()
64+
imageEncodingOptions: Base64ImageEncodingOptions? = null
6565
): JsonObject? {
6666
val values = frame.columns().map { col ->
6767
when (col) {
@@ -96,7 +96,7 @@ private val valueTypes =
9696
internal fun KlaxonJson.encodeValue(
9797
col: AnyCol,
9898
index: Int,
99-
imageEncodingOptions: ImageEncodingOptions = ImageEncodingOptions(encodeAsBase64 = false)
99+
imageEncodingOptions: Base64ImageEncodingOptions? = null
100100
): Any? = when {
101101
col.isList() -> col[index]?.let { list ->
102102
val values = (list as List<*>).map {
@@ -109,28 +109,26 @@ internal fun KlaxonJson.encodeValue(
109109
}
110110
array(values)
111111
} ?: array()
112+
112113
col.typeClass in valueTypes -> {
113114
val v = col[index]
114115
if ((v is Double && v.isNaN()) || (v is Float && v.isNaN())) {
115116
v.toString()
116117
} else v
117118
}
118119

119-
col.typeClass == BufferedImage::class -> col[index]?.let { image ->
120-
encodeBufferedImage(image as BufferedImage, imageEncodingOptions)
121-
} ?: ""
120+
col.typeClass == BufferedImage::class && imageEncodingOptions != null ->
121+
col[index]?.let { image ->
122+
encodeBufferedImageAsBase64(image as BufferedImage, imageEncodingOptions)
123+
} ?: ""
122124

123125
else -> col[index]?.toString()
124126
}
125127

126-
private fun encodeBufferedImage(
128+
private fun encodeBufferedImageAsBase64(
127129
image: BufferedImage,
128-
imageEncodingOptions: ImageEncodingOptions = ImageEncodingOptions()
130+
imageEncodingOptions: Base64ImageEncodingOptions = Base64ImageEncodingOptions()
129131
): String? {
130-
if (!imageEncodingOptions.encodeAsBase64) {
131-
return image.toString()
132-
}
133-
134132
return try {
135133
val preparedImage = if (imageEncodingOptions.isLimitSizeOn) {
136134
image.resizeKeepingAspectRatio(imageEncodingOptions.imageSizeLimit)
@@ -153,7 +151,7 @@ private fun encodeBufferedImage(
153151
internal fun KlaxonJson.encodeFrameWithMetadata(
154152
frame: AnyFrame,
155153
rowLimit: Int? = null,
156-
imageEncodingOptions: ImageEncodingOptions = ImageEncodingOptions()
154+
imageEncodingOptions: Base64ImageEncodingOptions? = null
157155
): JsonArray<*> {
158156
val valueColumn = frame.extractValueColumn()
159157
val arrayColumn = frame.extractArrayColumn()
@@ -253,7 +251,7 @@ internal fun KlaxonJson.encodeDataFrameWithMetadata(
253251
frame: AnyFrame,
254252
rowLimit: Int,
255253
nestedRowLimit: Int? = null,
256-
imageEncodingOptions: ImageEncodingOptions = ImageEncodingOptions()
254+
imageEncodingOptions: Base64ImageEncodingOptions? = null
257255
): JsonObject {
258256
return obj(
259257
VERSION to SERIALIZATION_VERSION,

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/json.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ public fun AnyFrame.toJson(prettyPrint: Boolean = false, canonical: Boolean = fa
276276
* Applied for each frame column recursively
277277
* @param prettyPrint Specifies whether the output JSON should be formatted with indentation and line breaks.
278278
* @param canonical Specifies whether the output JSON should be in a canonical form.
279-
* @param imageEncodingOptions The options for encoding images in the DataFrame. Defaults to encode images as Base64.
279+
* @param imageEncodingOptions The options for encoding images. The default is null, which indicates that the image is not encoded as Base64.
280280
*
281281
* @return The DataFrame converted to a JSON string with metadata.
282282
*/
@@ -285,7 +285,7 @@ public fun AnyFrame.toJsonWithMetadata(
285285
nestedRowLimit: Int? = null,
286286
prettyPrint: Boolean = false,
287287
canonical: Boolean = false,
288-
imageEncodingOptions: ImageEncodingOptions = ImageEncodingOptions(encodeAsBase64 = true)
288+
imageEncodingOptions: Base64ImageEncodingOptions? = null
289289
): String {
290290
return json {
291291
encodeDataFrameWithMetadata(this@toJsonWithMetadata, rowLimit, nestedRowLimit, imageEncodingOptions)
@@ -297,12 +297,10 @@ internal const val DEFAULT_IMG_SIZE = 600
297297
/**
298298
* Class representing the options for encoding images.
299299
*
300-
* @property encodeAsBase64 Specifies whether the images should be encoded as Base64. Defaults to false.
301300
* @property imageSizeLimit The maximum size to which images should be resized. Defaults to the value of DEFAULT_IMG_SIZE.
302301
* @property options Bitwise-OR of the [GZIP_ON] and [LIMIT_SIZE_ON] constants. Defaults to [GZIP_ON] or [LIMIT_SIZE_ON].
303302
*/
304-
public class ImageEncodingOptions(
305-
public val encodeAsBase64: Boolean = false,
303+
public class Base64ImageEncodingOptions(
306304
public val imageSizeLimit: Int = DEFAULT_IMG_SIZE,
307305
private val options: Int = GZIP_ON or LIMIT_SIZE_ON
308306
) {
@@ -313,6 +311,7 @@ public class ImageEncodingOptions(
313311
get() = options and LIMIT_SIZE_ON == LIMIT_SIZE_ON
314312

315313
public companion object {
314+
public const val ALL_OFF: Int = 0
316315
public const val GZIP_ON: Int = 1 // 2^0
317316
public const val LIMIT_SIZE_ON: Int = 2 // 2^1
318317
}

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/jupyter/Integration.kt

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,12 @@ import org.jetbrains.kotlinx.dataframe.dataTypes.IMG
4141
import org.jetbrains.kotlinx.dataframe.impl.codeGen.CodeGenerationReadResult
4242
import org.jetbrains.kotlinx.dataframe.impl.codeGen.urlCodeGenReader
4343
import org.jetbrains.kotlinx.dataframe.impl.createStarProjectedType
44-
import org.jetbrains.kotlinx.dataframe.impl.io.resizeKeepingAspectRatio
45-
import org.jetbrains.kotlinx.dataframe.impl.io.toBase64
46-
import org.jetbrains.kotlinx.dataframe.impl.io.toByteArray
4744
import org.jetbrains.kotlinx.dataframe.impl.renderType
4845
import org.jetbrains.kotlinx.dataframe.io.DataFrameHtmlData
4946
import org.jetbrains.kotlinx.dataframe.io.SupportedCodeGenerationFormat
5047
import org.jetbrains.kotlinx.dataframe.io.supportedFormats
5148
import org.jetbrains.kotlinx.jupyter.api.*
5249
import org.jetbrains.kotlinx.jupyter.api.libraries.*
53-
import java.awt.image.BufferedImage
5450
import kotlin.reflect.KClass
5551
import kotlin.reflect.KProperty
5652
import kotlin.reflect.KType
@@ -59,8 +55,6 @@ import kotlin.reflect.full.isSubtypeOf
5955
/** Users will get an error if their Kotlin Jupyter kernel is older than this version. */
6056
private const val MIN_KERNEL_VERSION = "0.11.0.198"
6157

62-
private const val DEFAULT_HTML_IMG_SIZE = 100
63-
6458
internal val newDataSchemas = mutableListOf<KClass<*>>()
6559

6660
internal class Integration(
@@ -209,19 +203,6 @@ internal class Integration(
209203
}
210204
}
211205

212-
notebook.renderersProcessor.registerWithoutOptimizing(
213-
createRenderer<BufferedImage> {
214-
val src = buildString {
215-
append("""data:image/$DEFAULT_HTML_IMG_SIZE;base64,""")
216-
append(
217-
it.resizeKeepingAspectRatio(DEFAULT_HTML_IMG_SIZE).toByteArray().toBase64()
218-
)
219-
}
220-
HTML("""<img src="$src"/>""")
221-
},
222-
ProcessingPriority.HIGHER
223-
)
224-
225206
with(JupyterHtmlRenderer(config.display, this)) {
226207
render<DisableRowsLimitWrapper>(
227208
{ "DataRow: index = ${it.value.rowsCount()}, columnsCount = ${it.value.columnsCount()}" },

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/jupyter/JupyterHtmlRenderer.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.jetbrains.kotlinx.dataframe.jupyter
33
import com.beust.klaxon.json
44
import org.jetbrains.kotlinx.dataframe.api.take
55
import org.jetbrains.kotlinx.dataframe.impl.io.encodeFrame
6+
import org.jetbrains.kotlinx.dataframe.io.Base64ImageEncodingOptions
67
import org.jetbrains.kotlinx.dataframe.io.DataFrameHtmlData
78
import org.jetbrains.kotlinx.dataframe.io.DisplayConfiguration
89
import org.jetbrains.kotlinx.dataframe.io.toHTML
@@ -74,7 +75,11 @@ internal inline fun <reified T : Any> JupyterHtmlRenderer.render(
7475
)
7576
}.toJsonString()
7677
} else {
77-
df.toJsonWithMetadata(limit, reifiedDisplayConfiguration.rowsLimit)
78+
df.toJsonWithMetadata(
79+
limit,
80+
reifiedDisplayConfiguration.rowsLimit,
81+
imageEncodingOptions = Base64ImageEncodingOptions()
82+
)
7883
}
7984
notebook.renderAsIFrameAsNeeded(html, staticHtml, jsonEncodedDf)
8085
} else {

0 commit comments

Comments
 (0)