Skip to content

Commit 245d690

Browse files
committed
Add option to downsize BufferedImage in HTML renderer
Introduced a configuration option to conditionally downsize BufferedImages in the HTML renderer. This mitigates potential memory problems by resizing images according to the provided DisplayConfiguration, specifically affecting the output of dataframes in the Kotlin Notebook plugin.
1 parent fe4b3c5 commit 245d690

File tree

2 files changed

+50
-2
lines changed
  • core
    • generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io
    • src/main/kotlin/org/jetbrains/kotlinx/dataframe/io

2 files changed

+50
-2
lines changed

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import org.jetbrains.kotlinx.dataframe.columns.ColumnWithPath
2121
import org.jetbrains.kotlinx.dataframe.columns.FrameColumn
2222
import org.jetbrains.kotlinx.dataframe.impl.DataFrameSize
2323
import org.jetbrains.kotlinx.dataframe.impl.columns.addPath
24+
import org.jetbrains.kotlinx.dataframe.impl.io.resizeKeepingAspectRatio
2425
import org.jetbrains.kotlinx.dataframe.impl.renderType
2526
import org.jetbrains.kotlinx.dataframe.impl.scale
2627
import org.jetbrains.kotlinx.dataframe.impl.truncate
@@ -31,6 +32,7 @@ import org.jetbrains.kotlinx.dataframe.name
3132
import org.jetbrains.kotlinx.dataframe.nrow
3233
import org.jetbrains.kotlinx.dataframe.size
3334
import java.awt.Desktop
35+
import java.awt.image.BufferedImage
3436
import java.io.File
3537
import java.io.InputStreamReader
3638
import java.net.URL
@@ -152,7 +154,8 @@ internal fun AnyFrame.toHtmlData(
152154
DataFrameReference(id, value.size)
153155
}
154156
} else {
155-
val html = formatter.format(value, cellRenderer, renderConfig)
157+
val html =
158+
formatter.format(downsizeBufferedImageIfNeeded(value, renderConfig), cellRenderer, renderConfig)
156159
val style = renderConfig.cellFormatter?.invoke(FormattingDSL, it, col)?.attributes()?.ifEmpty { null }
157160
?.joinToString(";") { "${it.first}:${it.second}" }
158161
HtmlContent(html, style)
@@ -180,6 +183,26 @@ internal fun AnyFrame.toHtmlData(
180183
return DataFrameHtmlData(style = "", body = body, script = script)
181184
}
182185

186+
private const val DEFAULT_HTML_IMG_SIZE = 100
187+
188+
/**
189+
* This method resizes a BufferedImage if necessary, according to the provided DisplayConfiguration.
190+
* It is essential to prevent potential memory problems when serializing HTML data for display in the Kotlin Notebook plugin.
191+
*
192+
* @param value The input value to be checked and possibly downsized.
193+
* @param renderConfig The DisplayConfiguration to determine if downsizing is needed.
194+
* @return The downsized BufferedImage if value is a BufferedImage and downsizing is enabled in the DisplayConfiguration,
195+
* otherwise returns the input value unchanged.
196+
*/
197+
private fun downsizeBufferedImageIfNeeded(value: Any?, renderConfig: DisplayConfiguration): Any? =
198+
when {
199+
value is BufferedImage && renderConfig.downsizeBufferedImage -> {
200+
value.resizeKeepingAspectRatio(DEFAULT_HTML_IMG_SIZE)
201+
}
202+
203+
else -> value
204+
}
205+
183206
/**
184207
* Renders [this] [DataFrame] as static HTML (meaning no JS is used).
185208
* CSS rendering is enabled by default but can be turned off using [includeCss]
@@ -568,6 +591,7 @@ public data class DisplayConfiguration(
568591
internal val localTesting: Boolean = flagFromEnv("KOTLIN_DATAFRAME_LOCAL_TESTING"),
569592
var useDarkColorScheme: Boolean = false,
570593
var enableFallbackStaticTables: Boolean = true,
594+
var downsizeBufferedImage: Boolean = true
571595
) {
572596
public companion object {
573597
public val DEFAULT: DisplayConfiguration = DisplayConfiguration()

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import org.jetbrains.kotlinx.dataframe.columns.ColumnWithPath
2121
import org.jetbrains.kotlinx.dataframe.columns.FrameColumn
2222
import org.jetbrains.kotlinx.dataframe.impl.DataFrameSize
2323
import org.jetbrains.kotlinx.dataframe.impl.columns.addPath
24+
import org.jetbrains.kotlinx.dataframe.impl.io.resizeKeepingAspectRatio
2425
import org.jetbrains.kotlinx.dataframe.impl.renderType
2526
import org.jetbrains.kotlinx.dataframe.impl.scale
2627
import org.jetbrains.kotlinx.dataframe.impl.truncate
@@ -31,6 +32,7 @@ import org.jetbrains.kotlinx.dataframe.name
3132
import org.jetbrains.kotlinx.dataframe.nrow
3233
import org.jetbrains.kotlinx.dataframe.size
3334
import java.awt.Desktop
35+
import java.awt.image.BufferedImage
3436
import java.io.File
3537
import java.io.InputStreamReader
3638
import java.net.URL
@@ -152,7 +154,8 @@ internal fun AnyFrame.toHtmlData(
152154
DataFrameReference(id, value.size)
153155
}
154156
} else {
155-
val html = formatter.format(value, cellRenderer, renderConfig)
157+
val html =
158+
formatter.format(downsizeBufferedImageIfNeeded(value, renderConfig), cellRenderer, renderConfig)
156159
val style = renderConfig.cellFormatter?.invoke(FormattingDSL, it, col)?.attributes()?.ifEmpty { null }
157160
?.joinToString(";") { "${it.first}:${it.second}" }
158161
HtmlContent(html, style)
@@ -180,6 +183,26 @@ internal fun AnyFrame.toHtmlData(
180183
return DataFrameHtmlData(style = "", body = body, script = script)
181184
}
182185

186+
private const val DEFAULT_HTML_IMG_SIZE = 100
187+
188+
/**
189+
* This method resizes a BufferedImage if necessary, according to the provided DisplayConfiguration.
190+
* It is essential to prevent potential memory problems when serializing HTML data for display in the Kotlin Notebook plugin.
191+
*
192+
* @param value The input value to be checked and possibly downsized.
193+
* @param renderConfig The DisplayConfiguration to determine if downsizing is needed.
194+
* @return The downsized BufferedImage if value is a BufferedImage and downsizing is enabled in the DisplayConfiguration,
195+
* otherwise returns the input value unchanged.
196+
*/
197+
private fun downsizeBufferedImageIfNeeded(value: Any?, renderConfig: DisplayConfiguration): Any? =
198+
when {
199+
value is BufferedImage && renderConfig.downsizeBufferedImage -> {
200+
value.resizeKeepingAspectRatio(DEFAULT_HTML_IMG_SIZE)
201+
}
202+
203+
else -> value
204+
}
205+
183206
/**
184207
* Renders [this] [DataFrame] as static HTML (meaning no JS is used).
185208
* CSS rendering is enabled by default but can be turned off using [includeCss]
@@ -568,6 +591,7 @@ public data class DisplayConfiguration(
568591
internal val localTesting: Boolean = flagFromEnv("KOTLIN_DATAFRAME_LOCAL_TESTING"),
569592
var useDarkColorScheme: Boolean = false,
570593
var enableFallbackStaticTables: Boolean = true,
594+
var downsizeBufferedImage: Boolean = true
571595
) {
572596
public companion object {
573597
public val DEFAULT: DisplayConfiguration = DisplayConfiguration()

0 commit comments

Comments
 (0)