Skip to content

Add image render #17

@michalharakal

Description

@michalharakal

PRD – Kotlin Notebook Image Rendering for Computer Vision

1. Overview

Goal:
Provide a simple, Kotlin-native, notebook-friendly way to render images inline inside Kotlin notebooks for computer vision workflows, comparable in ease of use to Python’s PIL.Image.show() or display().

Target users:

  • Kotlin Notebook users
  • Computer vision engineers / researchers
  • sk-ai-net / skainet-notebook community

Leverages Kotlin Notebook rich output APIs:
https://kotlinlang.org/docs/data-analysis-notebooks-output-formats.html#rich-text


2. Problem Statement

Kotlin notebooks currently lack a clean, idiomatic way to show images inline. Typical approaches:

Approach Issues
Swing/AWT (JFrame) Breaks notebook flow, not inline
Save to file + link Slow, inconvenient
Manual HTML + base64 Verbose and error-prone
Java interop hacks Not idiomatic Kotlin

2.1 Reference: Python PIL API

In Python notebooks the workflow is extremely concise and expressive:

from PIL import Image
from IPython.display import display

img = Image.open("sample.jpg")
display(img)

# or even simpler
img

Key qualities to match:

  • One-line image display
  • Inline notebook rendering
  • Automatic type recognition
  • No explicit HTML or GUI code
  • Natural REPL/notebook flow

The Kotlin solution must aim to replicate this experience from a developer ergonomics standpoint.


3. Objectives

Deliver a small utility module that:

  • Displays images inline in Kotlin notebooks
  • Feels idiomatic to Kotlin users
  • Matches Python PIL display ergonomics as closely as possible
  • Supports multiple input sources
  • Uses native notebook rich HTML output
  • Avoids Swing GUIs or window popups

4. User Stories

US-01 – Display from BufferedImage

display(img)

US-02 – Display from file path

display("assets/sample.jpg")

US-03 – Display from ByteArray

display(bytes)

US-04 – Display with sizing options

display(img) {
    width = 256
    height = 256
}

US-05 – Multi-image display

display(listOf(img1, img2, img3))

5. Functional Requirements

Supported Inputs

Source Type
Loaded images BufferedImage
Encoded image data ByteArray
Filesystem paths String, Path
Remote sources URL

Output Rendering

  • Render using <img> HTML tags
  • Inline base64 sources (data:image/*;base64)
  • Emitted via Kotlin Notebook rich output (HTML(...))

Styling Controls

Optional DSL settings:

  • width, height
  • alt text
  • border toggle
  • cssClass

Batch / Grid Mode

Lists render as responsive image rows or grids.


6. Non-Functional Requirements

Category Requirement
Performance <50ms encode + render per image
Platform JVM 11+
Notebook Compatible with skainet-notebook & IntelliJ Kotlin Notebooks
Dependencies No GUI frameworks required
Accessibility Support alt text

7. API Design

Core function

display(image: Any, options: DisplayOptions.() -> Unit = {})

Overloads

display(img: BufferedImage)
display(bytes: ByteArray)
display(path: String)
display(url: URL)
display(images: List<BufferedImage>)

Options DSL

class DisplayOptions {
    var width: Int? = null
    var height: Int? = null
    var alt: String = ""
    var border: Boolean = false
    var cssClass: String? = null
}

8. Implementation Strategy

Rendering model

<img src="data:image/png;base64,{base64}" />

Injected via:

HTML(htmlString).display()

Conversion utilities

fun BufferedImage.toBase64(): String
fun bytesToImage(bytes: ByteArray): BufferedImage

Dispatcher

fun display(obj: Any, options: DisplayOptions.() -> Unit = {}) {
    when (obj) {
        is BufferedImage -> render(obj, options)
        is ByteArray -> render(bytesToImage(obj), options)
        is String -> render(ImageIO.read(File(obj)), options)
        is URL -> render(ImageIO.read(obj), options)
        is List<*> -> renderGrid(obj.filterIsInstance<BufferedImage>())
        else -> error("Unsupported image source")
    }
}

Grid HTML layout

<div style="display:flex; gap:8px;">
  <img ... />
  <img ... />
  <img ... />
</div>

9. Example Notebook Usage

import skai.display.*

val img = ImageIO.read(File("sample.jpg"))

display(img)

display(img) {
    width = 256
    height = 256
    border = true
}

display(listOf(img, img, img))

10. Developer Experience

  • One-line image display
  • Python-PIL-style ergonomics
  • No pop-ups or windows
  • No file roundtrips
  • Pure inline notebook output

11. skainet-notebook Integration

Requires no kernel changes. Distributed as dependency:

@file:DependsOn("sk.ai:skainet-display:0.1.0")

12. Delivery Plan

Phase Output
M1 Base image renderer
M2 Multi-source support
M3 Grid + styling DSL
M4 Docs + notebook samples

13. Risks

Risk Mitigation
HTML restrictions SVG fallback
Memory usage Auto-downscale very large images
Performance issues Cache base64 results

14. Success Metrics

  • ≤5 lines to display any image
  • Zero Swing UI windows
  • Used in ≥3 real skainet demos

15. References


16. MVP Code Sketch

object KVImage {
    fun display(img: BufferedImage, opts: DisplayOptions = DisplayOptions()) {
        val base64 = img.toBase64()
        HTML("""
            <img src="data:image/png;base64,$base64"
                 ${opts.width?.let { "width='$it'" } ?: ""}
                 ${opts.height?.let { "height='$it'" } ?: ""}
                 alt="${opts.alt}"
                 style="${if (opts.border) "border:1px solid #ccc;" else ""}">
        """).display()
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions