Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 clroot

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
2 changes: 1 addition & 1 deletion README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,4 @@ result.save(Path::new("photo.webp"))?;

## 라이선스

MIT OR Apache-2.0
MIT
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,4 @@ result.save(Path::new("photo.webp"))?;

## License

MIT OR Apache-2.0
MIT
195 changes: 149 additions & 46 deletions bindings/kotlin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,21 @@ Supports macOS (Apple Silicon, Intel), Linux (x86_64, ARM64), and Windows (x86_6

## Installation

[![Maven Central](https://img.shields.io/maven-central/v/io.clroot.slimg/slimg-kotlin)](https://central.sonatype.com/artifact/io.clroot.slimg/slimg-kotlin)

### Gradle (Kotlin DSL)

```kotlin
dependencies {
implementation("io.clroot.slimg:slimg-kotlin:0.3.1")
implementation("io.clroot.slimg:slimg-kotlin:$slimgVersion")
}
```

### Gradle (Groovy)

```groovy
dependencies {
implementation 'io.clroot.slimg:slimg-kotlin:0.3.1'
implementation 'io.clroot.slimg:slimg-kotlin:$slimgVersion'
}
```

Expand All @@ -28,58 +30,160 @@ dependencies {
<dependency>
<groupId>io.clroot.slimg</groupId>
<artifactId>slimg-kotlin</artifactId>
<version>0.3.1</version>
<version>${slimgVersion}</version>
</dependency>
```

## Usage
## Quick Start

```kotlin
import io.clroot.slimg.*

// Decode an image file
val result = decodeFile("photo.jpg")
println("${result.image.width}x${result.image.height} ${result.format}")

// Convert to WebP
val webp = convert(result.image, PipelineOptions(
format = Format.WEB_P,
quality = 80u,
resize = null,
))
File("photo.webp").writeBytes(webp.data)

// Optimize in the same format
val fileBytes = File("photo.png").readBytes()
val optimized = optimize(fileBytes, 80u)
File("photo-optimized.png").writeBytes(optimized.data)

// Resize and convert
val resized = convert(result.image, PipelineOptions(
format = Format.WEB_P,
quality = 80u,
// Decode → transform → encode in a single chain
val result = SlimgImage.decodeFile("photo.jpg")
.resize(width = 800)
.crop(aspectRatio = 16 to 9)
.encode(Format.WEB_P, quality = 85)

File("photo.webp").writeBytes(result.data)
```

## Usage

### SlimgImage (Fluent API)

Chain decode → transform → encode operations fluently. All parameters use `Int` — no `UInt`/`UByte` conversions needed.

```kotlin
// Decode from various sources
val img = SlimgImage.decode(byteArray)
val img = SlimgImage.decode(inputStream)
val img = SlimgImage.decode(byteBuffer)
val img = SlimgImage.decodeFile("photo.jpg")

// Resize
img.resize(width = 800) // by width (preserve aspect ratio)
img.resize(height = 600) // by height
img.resize(width = 800, height = 600) // exact dimensions
img.resize(fit = 1200 to 1200) // fit within bounds
img.resize(scale = 0.5) // scale factor

// Crop
img.crop(aspectRatio = 16 to 9) // center-anchored
img.crop(x = 100, y = 50, width = 800, height = 600) // region

// Extend (add padding)
img.extend(1920, 1080) // to exact size
img.extend(aspectRatio = 1 to 1) // to aspect ratio
img.extend(1920, 1080, fill = Slimg.solidColor(255, 0, 0)) // with color

// Encode
img.encode(Format.WEB_P, quality = 85) // to specific format
img.optimize(quality = 70) // re-encode in source format
```

#### Chaining

Each operation returns a new `SlimgImage`, so you can chain freely:

```kotlin
val result = SlimgImage.decodeFile("photo.jpg")
.resize(fit = 1200 to 1200)
.crop(aspectRatio = 1 to 1)
.extend(aspectRatio = 16 to 9, fill = Slimg.solidColor(0, 0, 0))
.encode(Format.WEB_P, quality = 80)

File("output.webp").writeBytes(result.data)
```

#### With InputStream / ByteBuffer

Works with any byte source — file streams, network responses, WebFlux `DataBuffer`, etc.

```kotlin
// From InputStream
FileInputStream("photo.jpg").use { stream ->
val result = SlimgImage.decode(stream)
.resize(width = 800)
.encode(Format.WEB_P)
File("photo.webp").writeBytes(result.data)
}

// From ByteBuffer (e.g. WebFlux DataBuffer)
val result = SlimgImage.decode(dataBuffer.asByteBuffer())
.resize(fit = 1200 to 1200)
.encode(Format.WEB_P, quality = 85)
```

### Slimg Object

For one-off operations without chaining:

```kotlin
// Decode
val decoded = Slimg.decode(byteArray) // or InputStream, ByteBuffer
val decoded = Slimg.decodeFile("photo.jpg")

// Convert with simplified parameters
val result = Slimg.convert(
decoded.image,
Format.WEB_P,
quality = 85, // Int, not UByte
resize = ResizeMode.Width(800u),
))
crop = CropMode.AspectRatio(16u, 9u),
)

// Optimize
val optimized = Slimg.optimize(fileBytes, quality = 70) // Int quality

// Image operations
val resized = Slimg.resize(image, ResizeMode.Width(800u))
val cropped = Slimg.crop(image, CropMode.AspectRatio(16u, 9u))
val extended = Slimg.extend(image, ExtendMode.Size(1920u, 1080u))

// FillColor helper
val red = Slimg.solidColor(255, 0, 0) // Int RGBA, alpha defaults to 255
```

### Low-level Functions

Direct FFI bindings are also available as top-level functions:

```kotlin
val decoded = decode(byteArray)
val result = convert(image, PipelineOptions(Format.WEB_P, 80u.toUByte(), null, null, null, null))
val optimized = optimize(byteArray, 80u.toUByte())
```

## API Layers

| Layer | Use When | Example |
|-------|----------|---------|
| `SlimgImage` | Chaining multiple operations | `SlimgImage.decode(bytes).resize(width = 800).encode(Format.WEB_P)` |
| `Slimg` object | One-off operations with `Int` params | `Slimg.optimize(bytes, quality = 80)` |
| Top-level functions | Direct FFI access needed | `decode(bytes)`, `convert(image, options)` |

## API Reference

### Functions

| Function | Description |
|----------|-------------|
| `decode(data: ByteArray)` | Decode image from bytes |
| `decodeFile(path: String)` | Decode image from file path |
| `convert(image, options)` | Convert image to a different format |
| `crop(image, mode)` | Crop an image by region or aspect ratio |
| `extend(image, mode, fill)` | Extend (pad) image canvas |
| `resize(image, mode)` | Resize an image |
| `optimize(data: ByteArray, quality: UByte)` | Re-encode to reduce file size |
| `outputPath(input, format, output?)` | Generate output file path |
| `formatExtension(format)` | Get file extension for a format |
| `formatCanEncode(format)` | Check if format supports encoding |
| `formatFromExtension(path)` | Detect format from file extension |
| `formatFromMagicBytes(data)` | Detect format from file header |
### SlimgImage

| Method | Description |
|--------|-------------|
| `SlimgImage.decode(ByteArray / InputStream / ByteBuffer)` | Decode from bytes |
| `SlimgImage.decodeFile(path)` | Decode from file path |
| `SlimgImage.from(ImageData, format?)` | Wrap existing ImageData |
| `.resize(width?, height?)` | Resize by width/height/exact |
| `.resize(fit: Pair)` | Fit within bounds |
| `.resize(scale: Double)` | Scale by factor |
| `.crop(x, y, width, height)` | Crop region |
| `.crop(aspectRatio: Pair)` | Crop to aspect ratio |
| `.extend(width, height, fill?)` | Extend to exact size |
| `.extend(aspectRatio: Pair, fill?)` | Extend to aspect ratio |
| `.encode(format, quality?)` | Encode to format |
| `.optimize(quality?)` | Re-encode in source format |
| `.width` / `.height` | Current dimensions |
| `.format` | Source format (from decode) |
| `.imageData` | Underlying ImageData |

### Types

Expand All @@ -90,11 +194,10 @@ val resized = convert(result.image, PipelineOptions(
| `CropMode` | `Region`, `AspectRatio` |
| `ExtendMode` | `AspectRatio`, `Size` |
| `FillColor` | `Transparent`, `Solid(r, g, b, a)` |
| `PipelineOptions` | `format`, `quality`, `resize`, `crop`, `extend`, `fillColor` |
| `PipelineResult` | `data` (ByteArray), `format` |
| `DecodeResult` | `image` (ImageData), `format` |
| `ImageData` | `width`, `height`, `data` (raw pixels) |
| `SlimgException` | Error with subclasses: `UnsupportedFormat`, `UnknownFormat`, `EncodingNotSupported`, `Decode`, `Encode`, `Resize`, `Crop`, `Extend`, `Io`, `Image` |
| `ImageData` | `width`, `height`, `data` (raw RGBA pixels) |
| `SlimgException` | `UnsupportedFormat`, `UnknownFormat`, `EncodingNotSupported`, `Decode`, `Encode`, `Resize`, `Crop`, `Extend`, `Io`, `Image` |

## Supported Platforms

Expand All @@ -113,4 +216,4 @@ val resized = convert(result.image, PipelineOptions(

## License

MIT OR Apache-2.0
MIT
2 changes: 1 addition & 1 deletion bindings/kotlin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ mavenPublishing {

licenses {
license {
name.set("MIT OR Apache-2.0")
name.set("MIT")
url.set("https://github.com/clroot/slimg/blob/main/LICENSE")
}
}
Expand Down
2 changes: 1 addition & 1 deletion bindings/kotlin/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
group=io.clroot.slimg
version=0.3.1
version=0.4.0
kotlin.code.style=official
Loading
Loading