Skip to content

Releases: SolidKeyAB/kmpmedia

v1.15.0 — Gesture + physics interactivity

Choose a tag to compare

@ozgesolidkey ozgesolidkey released this 01 Oct 18:18

KMPMedia 1.15.0 — Gesture + physics interactivity

Clip media to any shape — now grab, drag, pinch-zoom, twist-rotate, fling and spring it back. The interactivity half of Bet 4 — Interactivity primitives on the roadmap, productized out of the demo.

One modifier does both halves:

  • Applies the pan / zoom / rotation through a single graphicsLayer (a GPU-layer transform — no recomposition, holds 60fps).
  • Drives it from a custom awaitEachGesture loop: one-/two-finger drag pans, pinch zooms (clamped), twist rotates, and on release the pan carries on with an exponential-decay momentum fling then springs back inside its bounds.

Pass an OGHitArea and the touch is shape-aware — a drag only starts inside the clip's silhouette, so the transparent corners of a triangle / circle / lasso fall through instead of grabbing it (and touches there pass to whatever is behind).

New API — com.solidkey.painpoints.gesture

  • Modifier.ogInteractive(state, hitArea?) — plus a convenience overload that remembers its own state.
  • OGInteractionState / rememberOGInteractionState(...) — live offset / scale / rotation Animatables you can read (HUD, other effects) + reset().
  • OGInteractionConfig (presets DragOnly / PanZoom / All) + OGPanBounds — size-independent pan limits (a fraction of content size, so one config works at any resolution).
  • OGHitArea — a normalized 0..1 hit region: RECT / CIRCLE / TRIANGLE_UP / TRIANGLE_DOWN / DIAMOND / polygon(...) / of(OGShapeType).

Zero new dependency; identical on Android & iOS. Purely additive — a new package, nothing existing changed. The hit-test + clamp maths are pure and unit-tested on JVM + iOS (16 tests); live-verified on Android (drag in all directions, pan bounds + spring settle, shape-aware grab/ignore).

implementation("se.solidkey:kmpmedia-lib:1.15.0")

See docs/INTERACTIVE.md and the full CHANGELOG.md. The demo's Interactive shapes screen dogfoods it.

v1.14.0 — Compositor export → H.264 MP4 🎬

Choose a tag to compare

@ozgesolidkey ozgesolidkey released this 01 Oct 08:51

Compositor export → H.264 MP4 🎬

The on-device compositor could already export a shareable animated GIF (1.13.0). 1.14.0 adds a second export target: the same OGComposition renders to a real H.264 MP4 with OGComposition.exportMp4() — driving the device's own video encoder, with no third-party dependency.

Added

  • OGComposition.exportMp4(bitRate = defaultMp4BitRate(width, height, fps), background = Color.Black) — renders every frame offscreen at the composition's fps, flattens it onto an opaque background (H.264 has no alpha), and encodes a real H.264 MP4 with the OS video encoder: Android MediaCodec + MediaMuxer, iOS AVAssetWriter. Returns the encoded ByteArray; saving/sharing stays the app's call.
  • defaultMp4BitRate(width, height, fps) — a resolution/fps-scaled default average bit rate, clamped to a 0.75–16 Mbps band.

The byte stream isn't identical across platforms (each OS has its own encoder), but the input is: compositing and the RGB→YUV / RGB→BGRA colour maths live in shared, tested commonMain, so both encoders compress the same rendered frames. H.264 needs even dimensions. Purely additive — one new extension, nothing existing changed. Completes Bet 3 — on-device compositor + export beyond GIF.

Verified: 10 new tests green on JVM + iOS; Android live (MediaCodec → a valid 288² @20fps 40-frame H.264, played back through OGAVPlayer); iOS live (AVAssetWriter on the simulator → a valid standard High-profile H.264, ffprobe-confirmed). See docs/COMPOSITOR.md.

implementation("se.solidkey:kmpmedia-lib:1.14.0")

v1.13.0 — On-device compositor + GIF export 📽️

Choose a tag to compare

@ozgesolidkey ozgesolidkey released this 30 Sep 20:59

On-device compositor + GIF export — the flagship "2.0" 📽️

KMPMedia could put one piece of media in one shape. 1.13.0 adds Bet 3: compose several layers on one timeline, animate them with keyframes, preview the result live at 60fps, and export it to a shareable animated GIF — all authored on device. No other KMP library does compose-and-export.

Added

  • OGComposition — a fixed width × height canvas, a durationMs timeline at fps, a background colour, and a back-to-front stack of OGCompositionLayers. Each layer is a still image or a solid fill, placed in a box whose x / y / scale / rotation / opacity are each an animatable keyframe track, optionally clipped to any KMPMedia shape (built-in, lasso, multi-region, CircleShape, …), visible over [startMs, endMs].
  • OGCompositionView — plays the composition on the Compose frame clock (holds 60fps, no intermediate bitmap) or acts as a scrubber via positionMs.
  • OGComposition.exportGif() — renders every frame offscreen and encodes one looping animated GIF with the built-in pure-Kotlin OGGifEncoder (median-cut palette + LZW) — no dependency, no platform encoder, identical bytes on Android and iOS. Returns the ByteArray; where to save/share is the app's call. renderFrame(timeMs) grabs any single frame.
  • OGKeyframedFloat / OGKeyframe / OGEasing — an animatable float track (LINEAR / EASE_IN / EASE_OUT / EASE_IN_OUT), parse-nothing / lerp-per-frame.
  • OGGifEncoder — a standalone, dependency-free GIF89a encoder usable on its own for any frame list.

Everything is pure commonMain and purely additive (new com.solidkey.painpoints.compositor package, nothing existing changed). 19 tests green on JVM + iOS. See docs/COMPOSITOR.md. MP4 / frame-sequence export are the planned follow-ups.

implementation("se.solidkey:kmpmedia-lib:1.13.0")

v1.12.0 — Soft & gradient masks, multi-region clips

Choose a tag to compare

@ozgesolidkey ozgesolidkey released this 30 Sep 11:18

Bet 2 — Living shapes: soft & gradient masks, multi-region clips.

A clip mask no longer has to be one shape with a hard edge:

  • OGMultiRegionShape (com.solidkey.painpoints.shape) — combine several placed sub-shapes with a path op (union / intersect / difference / xor) into one clip: two portholes, or a diamond with a circular bite. A plain Compose Shape, so it drops into the existing clipShape slot for image, GIF and video with zero consumer change. Outlines combined once per size, never per frame. Factories union(...) / of(...); OGClipRegion places a sub-shape in a normalized 0..1 sub-rect.
  • Modifier.ogSoftClip(shape, feather) + ogGradientMask(brush) (com.solidkey.painpoints.mask), surfaced on OGImageView as softEdge / maskBrush — feather a clip edge into a vignette, or fade content along a gradient, via one offscreen BlendMode.DstIn pass (no blur, no RenderEffect, no Android API floor). Scoped to Compose-drawn content (photo/GIF); video stays hard-clipped.

Zero new dependencies. 13 new tests green on JVM + iOS. Purely additive — every existing call compiles unchanged.

implementation("se.solidkey:kmpmedia-lib:1.12.0")

Docs: docs/SOFT_MASKS.md. See the README for live Android + iOS demo clips.

v1.11.0 — AI hooks: describe → shape / patch 🤖

Choose a tag to compare

@ozgesolidkey ozgesolidkey released this 29 Sep 22:16

AI hooks — "describe → shape / patch." KMPMedia's vector primitives are just data, so a language model can produce them. New com.solidkey.painpoints.ai.OGAiVector is a provider-agnostic JSON interop layer: it hands a model the exact contract and turns the reply back into a live clip shape or SVG patch.

  • polygonPrompt(instruction) / svgPatchPrompt(instruction, nodeIds) — build a prompt stating the JSON schema.
  • decodePolygon / decodeSvgPatch (+ …OrNull) — a model's reply → OGPolygonShape for clipShape, or Map<String, OGSvgNodeOverride> for OGSVGView(overrides = …). Tolerant of the ```json fences + prose models add.
  • encodePolygon / encodeSvgPatch — serialize the other way (persist a lasso, seed a prompt).
  • OGPolygonSpec / OGNodeOverrideSpec / OGSvgPatchSpec — @Serializable DTOs.

No network calls and no AI SDK in the library — it defines the contract and does the JSON; you own the model and the call. Purely additive (new package), zero-dependency beyond the serialization family already in use. Colors reuse the SVG color parser (#RGB/#RRGGBB/#AARRGGBB/rgb()/name). All work happens at generate/patch time, never per frame. 12 tests green on JVM + iOS.

First shipped piece of Bet 1 — Runtime & AI-editable vector's "AI hooks" item. See docs/AI_HOOKS.md.

implementation("se.solidkey:kmpmedia-lib:1.11.0")

v1.10.0 — Shape-morph clips (OGMorphShape) 🫧

Choose a tag to compare

@ozgesolidkey ozgesolidkey released this 27 Sep 12:10

Morph the clip mask itself

New this release: OGMorphShape — a Compose Shape whose outline morphs from one shape to another as you drive progress 0f → 1f. Because every KMPMedia media surface clips through a plain Shape (OGImageView.clipShape, OGPlayerConfig.clipShape, any Modifier.clip), it drops straight into that existing slot — so a live video, GIF or photo can be masked by an outline that animates circle → diamond → triangle → lasso while the media keeps playing, with no new surface API.

  • OGMorphShape(from, to, progress) — morph a mask between any two shapes (built-ins, a RoundedCornerShape, or an OGPolygonShape lasso).
  • ogMorphSequence(stops, progress) — one progress walks a whole chain of shapes for a seamless multi-stop loop.
  • Drive progress from any Compose animation (animateFloatAsState, an infinite transition, a slider).

Built for 60fps (the perf + simplicity gate)

Each endpoint outline is resampled once into equally-spaced perimeter points and cached by (from, to, size); per frame only those two point lists are lerped — no re-measure, no allocation churn. At progress ≤ 0 / ≥ 1 the raw endpoint outline passes straight through, so a settled clip costs nothing. Mismatched winding is auto-aligned so shapes morph without swirling. Same sample-once / lerp-per-frame approach as the SVG path morph.

Install

implementation("se.solidkey:kmpmedia-lib:1.10.0")

Identical on Android & iOS. See docs/SHAPE_MORPH_CLIPS.md. Tests green on JVM host + iOS; dogfooded in the demo's "Morph the clip itself" screen and the UFO Dodge shape-shifting hazard.

v1.9.0 — SVG path morphing

Choose a tag to compare

@ozgesolidkey ozgesolidkey released this 24 Sep 20:25

Path morphing — a <path> tweens smoothly between two shapes at runtime.

Set OGSvgNodeOverride.pathDataTo to a target d and drive morphProgress 0→1 from any Compose animation; the node interpolates from its current geometry to the target. Built for 60fps: both endpoint d strings are parsed once and cached, so each frame only lerps floats — no re-parse, no allocation churn. Structurally-mismatched paths snap at the halfway point rather than drawing garbage.

Purely additive (two new optional fields). Verified on Android + iOS (17 new unit tests) and dogfooded as a shape-shifting hazard in the demo's UFO Dodge game.

implementation("se.solidkey:kmpmedia-lib:1.9.0")

See docs/RUNTIME_SVG.md and CHANGELOG.

v1.8.0 — Runtime SVG path `d` override

Choose a tag to compare

@ozgesolidkey ozgesolidkey released this 23 Sep 20:43

Runtime path geometry. Runtime-editable SVG can now change a node's shape, not just its paint and transform. An OGSvgNodeOverride may carry a replacement path d, and the addressed <path> re-parses to the new geometry live — no new node, no re-parse of the whole SVG. This is the building block for path morphing (the next roadmap step).

Purely additive: a new optional pathData field defaulting to null, so every existing call is unchanged. Same code on Android and iOS.

Added

  • OGSvgNodeOverride.pathData: String? — a replacement path d. When set on a node holding <path> geometry, the node redraws with this geometry instead of its original; null (default) keeps the original. Ignored on non-path nodes.

Notes

  • The new d is re-parsed against the SVG's viewBox (same user-space as the source), so the viewport transform and any paint/transform override on the node still apply on top. Reuses parsePathCommands; the resolve rebuilds a fresh shapes list and never mutates the parsed source tree.
  • Callers can compute a d per frame (e.g. interpolate between two same-structure paths) as a stop-gap until first-class path-morphing lands.
implementation("se.solidkey:kmpmedia-lib:1.8.0")

Cumulative superset — also includes SVG runtime overrides, image/GIF/video playback + clip-to-shape, depth-layer, polygon lasso, and audio sprites.

v1.7.0 — Runtime-editable SVG 🎨

Choose a tag to compare

@ozgesolidkey ozgesolidkey released this 23 Sep 14:31

🎨 Runtime-editable SVG

KMPMedia parses an SVG into a live node tree — so from 1.7.0 you can address any node by its id and change its attributes at runtime, bound to Compose state. A static .svg becomes a live template: gauges, charts, progress rings, status icons. The source is parsed once; only the overrides change and it redraws live. Same code on Android and iOS.

OGSVGView(
    source = OGSvgUrlType("gauge.svg"),
    width = 300f, height = 195f,
    overrides = mapOf(
        "needle" to OGSvgNodeOverride(rotation = angle, rotationCx = 100f, rotationCy = 105f),
        "arc"    to OGSvgNodeOverride(stroke = zoneColor),
        "status" to OGSvgNodeOverride(fill = zoneColor),
    ),
)
  • OGSvgNodeOverride — a per-node override; every field nullable (null = keep original): fill, stroke, strokeWidth, translateX/translateY, rotation (+ rotationCx/rotationCy pivot), scaleX/scaleY.
  • Change the overrides map from state → only the matched nodes re-resolve, no re-parse of the source.
  • Paint folds into the node's style; transforms apply uniformly at draw time, so they work on any shape type.
  • Purely additive — overrides defaults to empty, so every existing OGSVGView call is unchanged.

Install

implementation("se.solidkey:kmpmedia-lib:1.7.0")

Cumulative superset — also includes animated GIF playback, video/image playback & seek, depth-layer, polygon lasso, video clip-to-shape and audio sprites from prior releases.

📖 Docs: docs/RUNTIME_SVG.md · 🗺️ ROADMAP · 📝 CHANGELOG

🎥 See the live gauge (needle + zones driven by a slider, Android + iOS) in the demo app's Runtime-editable SVG screen.

v1.6.0 — Animated GIF 🎞️

Choose a tag to compare

@ozgesolidkey ozgesolidkey released this 23 Sep 12:24

🎞️ Animated GIF playback (Android + iOS)

KMPMedia now plays animated GIFs — on both Android and iOS, from the same one-liner. Before 1.6.0 the image path decoded only a GIF's first frame (a still picture); now the frames actually animate, looping, with the same API as any other image.

OGImageView(
    source = OGImageUrlType("https://example.com/loading.gif"),
    onEventTriggered = { _, _ -> },
    onError = { },
)
  • Point OGImageView at a .gif (URL, file path, or iOS resource) and it plays — no API change to existing calls.
  • New rememberOGAnimatedPainter(source, loop = true, speed = 1f, onError = null) in com.solidkey.painpoints.image.gif if you want the playing Painter directly.
  • The same shape clip that crops a still photo works on the moving frames too — a GIF animating inside a circle / diamond.
  • No new dependencies — native decoders: Android AnimatedImageDrawable (API 28+, static first-frame fallback on 24–27), iOS Skia Codec driven by a small, unit-tested frame clock (OGGifClock).
  • Not GIF-specific: animated WebP and other multi-frame formats ride the same path.

Install

implementation("se.solidkey:kmpmedia-lib:1.6.0")

Cumulative superset — also includes the SVG + image/video playback & seek, depth-layer, polygon lasso, video clip-to-shape and audio-sprite features from prior releases.

📖 Docs: docs/GIF.md · 📝 CHANGELOG

🎥 See it in action (Android + iOS captures): the demo app "Animated GIF" screen.