Skip to content

Comments

feat(kotlin): add ergonomic API with SlimgImage fluent chaining#7

Merged
clroot merged 7 commits intomainfrom
feat/kotlin-ergonomic-api
Feb 18, 2026
Merged

feat(kotlin): add ergonomic API with SlimgImage fluent chaining#7
clroot merged 7 commits intomainfrom
feat/kotlin-ergonomic-api

Conversation

@clroot
Copy link
Owner

@clroot clroot commented Feb 18, 2026

Summary

  • SlimgImage fluent API 추가 — decode → resize/crop/extend → encode 체이닝 지원
  • Slimg wrapper object 추가 — Int 파라미터로 UByte/UInt 변환 불필요
  • InputStream/ByteBuffer 오버로드 추가 — WebFlux DataBuffer 등과 통합 용이
  • Kotlin README 전면 재작성 (새 API 기준, 버전 하드코딩 제거)
  • 라이센스 MIT OR Apache-2.0MIT 변경 + LICENSE 파일 생성
  • 버전 0.3.10.4.0 bump

Test plan

  • SlimgStreamsTest — InputStream/ByteBuffer 오버로드 테스트
  • SlimgObjectTest — Slimg wrapper object 테스트
  • SlimgImageTest — fluent API 체이닝 테스트
  • 기존 SlimgTest 전체 통과 확인
  • cargo check 빌드 확인

🤖 Generated with Claude Code

decode, optimize, formatFromMagicBytes 함수에 InputStream 및
ByteBuffer 오버로드를 추가하여 WebFlux DataBuffer 등과의
통합을 용이하게 함
UByte/UInt 변환 없이 Int로 quality를 전달하고,
PipelineOptions 대신 개별 파라미터로 convert를 호출할 수 있는
래퍼 object 추가. solidColor 헬퍼 포함.
decode → resize/crop/extend → encode 흐름을 체이닝으로 연결하는
SlimgImage 래퍼 클래스 추가. Int 파라미터로 UInt/UByte 변환 불필요.
Quick Start, 3단계 API 레이어 설명, InputStream/ByteBuffer 예시,
SlimgImage 체이닝 패턴 등 새 API 기준으로 전면 재작성
…al badge

릴리스마다 문서 버전 수정 누락 방지
Kotlin ergonomic API (SlimgImage fluent API, Slimg wrapper object,
InputStream/ByteBuffer overloads) 추가에 따른 minor version bump
LICENSE 파일 생성 및 모든 매니페스트/문서에서 라이센스 참조 변경
@clroot clroot merged commit d19d225 into main Feb 18, 2026
@gemini-code-assist
Copy link

Summary of Changes

Hello @clroot, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the Kotlin bindings for the slimg image optimization library by introducing a more idiomatic and user-friendly API. It provides a fluent interface for chaining image transformations and a simplified object for one-off operations, both designed to abstract away low-level type conversions. Additionally, it improves compatibility with common data streams by adding support for InputStream and ByteBuffer. The documentation has been thoroughly updated to guide users through these new features, and the project's licensing has been streamlined for clarity.

Highlights

  • Ergonomic Kotlin API: Introduced a new SlimgImage fluent API for chaining image operations (decode, resize, crop, extend, encode) and a Slimg wrapper object to simplify one-off operations, both eliminating the need for UByte/UInt conversions by using standard Int parameters.
  • Enhanced Input/Output Support: Added InputStream and ByteBuffer overloads for image decoding and optimization, improving integration with various data sources like WebFlux DataBuffer.
  • Documentation Update: The Kotlin README has been completely rewritten to reflect the new API structure and usage, including updated installation instructions and removal of hardcoded version numbers.
  • License Simplification: The project license has been changed from MIT OR Apache-2.0 to solely MIT, and a dedicated LICENSE file has been created at the repository root.
  • Version Bump: The project version across all relevant packages (slimg, slimg-core, slimg-ffi) has been updated from 0.3.1 to 0.4.0.
Changelog
  • Cargo.lock
    • Updated slimg package version to 0.4.0.
    • Updated slimg-core package version to 0.4.0.
    • Updated slimg-ffi package version to 0.4.0.
  • LICENSE
    • Added a new MIT License file.
  • README.ko.md
    • Updated the license declaration to MIT.
  • README.md
    • Updated the license declaration to MIT.
  • bindings/kotlin/README.md
    • Added Maven Central badge to installation section.
    • Updated Gradle dependency version to use a variable $slimgVersion.
    • Updated Maven dependency version to use a variable ${slimgVersion}.
    • Rewrote the 'Usage' section to introduce 'Quick Start' and detail the new SlimgImage fluent API and Slimg object.
    • Added sections for 'Chaining' and 'With InputStream / ByteBuffer' examples.
    • Introduced 'API Layers' table to differentiate between SlimgImage, Slimg object, and top-level functions.
    • Revised 'API Reference' to document SlimgImage methods and updated ImageData description.
    • Removed PipelineOptions from 'Types' reference.
  • bindings/kotlin/build.gradle.kts
    • Updated the license name in the publishing configuration to MIT.
  • bindings/kotlin/gradle.properties
    • Bumped the project version to 0.4.0.
  • bindings/kotlin/src/main/kotlin/io/clroot/slimg/Slimg.kt
    • Added a new Slimg object providing ergonomic wrappers for FFI functions.
    • Implemented decode overloads for ByteArray, InputStream, and ByteBuffer.
    • Implemented optimize overloads for ByteArray, InputStream, and ByteBuffer with Int quality parameter.
    • Implemented convert with Int quality parameter and optional transformation modes.
    • Implemented crop, resize, and extend functions for ImageData.
    • Added format utility functions (formatExtension, formatCanEncode, formatFromExtension, formatFromMagicBytes, outputPath).
    • Added solidColor helper function for creating FillColor from Int RGBA values.
    • Added a private Int.toQuality() extension function for quality clamping.
  • bindings/kotlin/src/main/kotlin/io/clroot/slimg/SlimgImage.kt
    • Added a new SlimgImage class for fluent chaining of image operations.
    • Implemented decode factory methods for ByteArray, InputStream, ByteBuffer, and file paths.
    • Implemented from factory method to wrap existing ImageData.
    • Added resize overloads for width, height, exact dimensions, fit, and scale.
    • Added crop overloads for region and aspect ratio.
    • Added extend overloads for exact size and aspect ratio with optional fill color.
    • Implemented encode to convert the image to a specified format and quality.
    • Implemented optimize to re-encode in the original source format.
    • Added width, height, format, and imageData properties.
  • bindings/kotlin/src/main/kotlin/io/clroot/slimg/SlimgStreams.kt
    • Added extension functions for InputStream to decode, optimize, and formatFromMagicBytes.
    • Added extension functions for ByteBuffer to decode, optimize, and formatFromMagicBytes.
    • Implemented a private ByteBuffer.toByteArray() extension function.
  • bindings/kotlin/src/test/kotlin/io/clroot/slimg/SlimgImageTest.kt
    • Added new test cases for SlimgImage factory methods, including decode from ByteArray, InputStream, ByteBuffer, and from existing ImageData.
    • Added tests for resize by width, height, exact, fit, and scale, including argument validation.
    • Added tests for crop by region and aspect ratio.
    • Added tests for extend to size and aspect ratio, including with solid color fill.
    • Added tests for encode to a specific format and optimize in source format, including error handling for unknown format.
    • Added comprehensive tests for chaining multiple operations, preserving source format, and immutability.
  • bindings/kotlin/src/test/kotlin/io/clroot/slimg/SlimgObjectTest.kt
    • Added new test cases for Slimg object's decode from ByteArray, InputStream, and ByteBuffer.
    • Added tests for optimize with Int quality and default quality, including clamping of out-of-range values.
    • Added tests for convert with defaults, resize, crop, and quality parameters.
    • Added tests for crop, resize, and extend image operations.
    • Added tests for solidColor helper function with Int values and custom alpha.
    • Added tests for format utility functions (formatFromExtension, formatCanEncode, formatFromMagicBytes).
  • bindings/kotlin/src/test/kotlin/io/clroot/slimg/SlimgStreamsTest.kt
    • Added new test cases for decode(InputStream) and decode(ByteBuffer) to verify consistency with decode(ByteArray).
    • Added tests for optimize(InputStream) and optimize(ByteBuffer).
    • Added tests for formatFromMagicBytes(InputStream) and formatFromMagicBytes(ByteBuffer) for format detection and unknown data.
    • Added tests to ensure ByteBuffer position is not modified after decoding.
    • Added test for decoding direct ByteBuffer.
  • bindings/python/README.md
    • Updated the license declaration to MIT.
  • bindings/python/pyproject.toml
    • Updated the project version to 0.4.0.
    • Changed the license text to MIT and removed the Apache Software License classifier.
  • cli/Cargo.toml
    • Updated the package version to 0.4.0.
    • Changed the license to MIT.
    • Updated the slimg-core dependency version to 0.4.0.
  • crates/slimg-core/Cargo.toml
    • Updated the package version to 0.4.0.
    • Changed the license to MIT.
  • crates/slimg-core/README.md
    • Updated the license declaration to MIT.
  • crates/slimg-ffi/Cargo.toml
    • Updated the package version to 0.4.0.
    • Changed the license to MIT.
Activity
  • The pull request author clroot has implemented new ergonomic APIs for Kotlin bindings, including a fluent chaining interface and a simplified wrapper object.
  • The author has added support for InputStream and ByteBuffer in the Kotlin bindings to improve integration with various data sources.
  • The Kotlin README has been thoroughly updated to document the new API features and usage examples.
  • The project's licensing has been updated to MIT across all relevant files and a new LICENSE file was added.
  • The project version has been bumped to 0.4.0 in Cargo.lock, gradle.properties, and various Cargo.toml files.
  • Extensive unit tests have been added for the new SlimgImage fluent API, Slimg object, and InputStream/ByteBuffer overloads.
  • The author confirmed that existing SlimgTest cases passed and cargo check verified the build.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

이 PR은 Kotlin 바인딩에 SlimgImage fluent API와 Slimg 래퍼 객체를 추가하여 API 사용성을 크게 향상시켰습니다. 또한 InputStreamByteBuffer 오버로드를 추가하여 다양한 데이터 소스와의 통합을 용이하게 한 점도 좋습니다. 코드 변경 사항은 전반적으로 깔끔하고, 테스트도 잘 작성되었습니다. 몇 가지 입력값 검증 로직을 추가하여 API의 안정성을 더욱 높일 수 있는 부분을 제안드립니다.

Comment on lines +60 to +68
fun resize(width: Int? = null, height: Int? = null): SlimgImage {
val mode = when {
width != null && height != null -> ResizeMode.Exact(width.toUInt(), height.toUInt())
width != null -> ResizeMode.Width(width.toUInt())
height != null -> ResizeMode.Height(height.toUInt())
else -> throw IllegalArgumentException("At least one of width or height must be specified")
}
return transformed(io.clroot.slimg.resize(imageData, mode))
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

음수 width 또는 height 값이 toUInt()를 통해 예기치 않은 큰 양수로 변환될 수 있습니다. 이는 의도치 않은 리사이즈 동작이나 오류를 발생시킬 수 있습니다. require를 사용하여 widthheight가 양수인지 확인하는 로직을 추가하는 것이 좋습니다.

    fun resize(width: Int? = null, height: Int? = null): SlimgImage {
        require(width == null || width > 0) { "Width must be positive." }
        require(height == null || height > 0) { "Height must be positive." }
        val mode = when {
            width != null && height != null -> ResizeMode.Exact(width.toUInt(), height.toUInt())
            width != null -> ResizeMode.Width(width.toUInt())
            height != null -> ResizeMode.Height(height.toUInt())
            else -> throw IllegalArgumentException("At least one of width or height must be specified")
        }
        return transformed(io.clroot.slimg.resize(imageData, mode))
    }

Comment on lines +78 to +81
fun resize(fit: Pair<Int, Int>): SlimgImage {
val mode = ResizeMode.Fit(fit.first.toUInt(), fit.second.toUInt())
return transformed(io.clroot.slimg.resize(imageData, mode))
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

fitPair에 음수 값이 전달될 경우, toUInt()에 의해 의도치 않은 큰 값으로 변환될 수 있습니다. require를 사용하여 fit의 두 값이 모두 양수인지 검증하는 것이 안전합니다.

    fun resize(fit: Pair<Int, Int>): SlimgImage {
        require(fit.first > 0 && fit.second > 0) { "Fit dimensions must be positive." }
        val mode = ResizeMode.Fit(fit.first.toUInt(), fit.second.toUInt())
        return transformed(io.clroot.slimg.resize(imageData, mode))
    }

Comment on lines +86 to +88
@Throws(SlimgException::class)
fun resize(scale: Double): SlimgImage =
transformed(io.clroot.slimg.resize(imageData, ResizeMode.Scale(scale)))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

scale 값이 0 이하일 경우 리사이즈가 제대로 동작하지 않거나 오류가 발생할 수 있습니다. scale 값이 양수인지 확인하는 검증 로직을 추가하는 것이 좋습니다.

    @Throws(SlimgException::class)
    fun resize(scale: Double): SlimgImage {
        require(scale > 0) { "Scale must be positive." }
        return transformed(io.clroot.slimg.resize(imageData, ResizeMode.Scale(scale)))
    }

Comment on lines +96 to +99
fun crop(x: Int, y: Int, width: Int, height: Int): SlimgImage {
val mode = CropMode.Region(x.toUInt(), y.toUInt(), width.toUInt(), height.toUInt())
return transformed(io.clroot.slimg.crop(imageData, mode))
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

crop 파라미터에 음수 값이 전달될 경우 toUInt() 변환 시 예기치 않은 동작을 유발할 수 있습니다. x, y는 0 이상, width, height는 0보다 큰 값이어야 합니다. require를 사용하여 입력값을 검증하는 것이 좋습니다.

    fun crop(x: Int, y: Int, width: Int, height: Int): SlimgImage {
        require(x >= 0 && y >= 0) { "Crop coordinates (x, y) must be non-negative." }
        require(width > 0 && height > 0) { "Crop dimensions (width, height) must be positive." }
        val mode = CropMode.Region(x.toUInt(), y.toUInt(), width.toUInt(), height.toUInt())
        return transformed(io.clroot.slimg.crop(imageData, mode))
    }

Comment on lines +109 to +112
fun crop(aspectRatio: Pair<Int, Int>): SlimgImage {
val mode = CropMode.AspectRatio(aspectRatio.first.toUInt(), aspectRatio.second.toUInt())
return transformed(io.clroot.slimg.crop(imageData, mode))
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

aspectRatio에 음수 값이 전달되면 toUInt() 변환 시 의도치 않은 동작을 할 수 있습니다. require를 사용하여 aspectRatio의 두 값이 모두 양수인지 검증하는 것이 좋습니다.

    fun crop(aspectRatio: Pair<Int, Int>): SlimgImage {
        require(aspectRatio.first > 0 && aspectRatio.second > 0) { "Aspect ratio components must be positive." }
        val mode = CropMode.AspectRatio(aspectRatio.first.toUInt(), aspectRatio.second.toUInt())
        return transformed(io.clroot.slimg.crop(imageData, mode))
    }

Comment on lines +120 to +127
fun extend(
width: Int,
height: Int,
fill: FillColor = FillColor.Transparent,
): SlimgImage {
val mode = ExtendMode.Size(width.toUInt(), height.toUInt())
return transformed(io.clroot.slimg.extend(imageData, mode, fill))
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

extendwidth, height에 음수 값이 전달되면 toUInt() 변환 시 예기치 않은 동작을 할 수 있습니다. require를 사용하여 두 값이 모두 양수인지 검증하는 것이 좋습니다.

    fun extend(
        width: Int,
        height: Int,
        fill: FillColor = FillColor.Transparent,
    ): SlimgImage {
        require(width > 0 && height > 0) { "Extend dimensions (width, height) must be positive." }
        val mode = ExtendMode.Size(width.toUInt(), height.toUInt())
        return transformed(io.clroot.slimg.extend(imageData, mode, fill))
    }

Comment on lines +137 to +143
fun extend(
aspectRatio: Pair<Int, Int>,
fill: FillColor = FillColor.Transparent,
): SlimgImage {
val mode = ExtendMode.AspectRatio(aspectRatio.first.toUInt(), aspectRatio.second.toUInt())
return transformed(io.clroot.slimg.extend(imageData, mode, fill))
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

extendaspectRatio에 음수 값이 전달되면 toUInt() 변환 시 예기치 않은 동작을 할 수 있습니다. require를 사용하여 aspectRatio의 두 값이 모두 양수인지 검증하는 것이 좋습니다.

    fun extend(
        aspectRatio: Pair<Int, Int>,
        fill: FillColor = FillColor.Transparent,
    ): SlimgImage {
        require(aspectRatio.first > 0 && aspectRatio.second > 0) { "Aspect ratio components must be positive." }
        val mode = ExtendMode.AspectRatio(aspectRatio.first.toUInt(), aspectRatio.second.toUInt())
        return transformed(io.clroot.slimg.extend(imageData, mode, fill))
    }

Comment on lines +119 to +120
fun solidColor(r: Int, g: Int, b: Int, a: Int = 255): FillColor =
FillColor.Solid(r.toUByte(), g.toUByte(), b.toUByte(), a.toUByte())

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

RGBA 색상 컴포넌트가 0..255 범위를 벗어나는 경우 toUByte()에 의해 예기치 않은 값으로 변환될 수 있습니다. (예: 256.toUByte()0u, (-1).toUByte()255u). require를 사용하여 각 컴포넌트가 0..255 범위 내에 있는지 검증하면 API를 더 안전하게 만들 수 있습니다.

    fun solidColor(r: Int, g: Int, b: Int, a: Int = 255): FillColor {
        require(r in 0..255) { "Red component must be in range 0-255." }
        require(g in 0..255) { "Green component must be in range 0-255." }
        require(b in 0..255) { "Blue component must be in range 0-255." }
        require(a in 0..255) { "Alpha component must be in range 0-255." }
        return FillColor.Solid(r.toUByte(), g.toUByte(), b.toUByte(), a.toUByte())
    }

@clroot clroot deleted the feat/kotlin-ergonomic-api branch February 19, 2026 04:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant