{"id":13483637,"url":"https://github.com/Kamel-Media/Kamel","last_synced_at":"2025-03-27T14:31:28.302Z","repository":{"id":42058036,"uuid":"327244529","full_name":"Kamel-Media/Kamel","owner":"Kamel-Media","description":"Kotlin asynchronous media loading and caching library for Compose. ","archived":false,"fork":false,"pushed_at":"2024-05-22T06:59:08.000Z","size":1473,"stargazers_count":555,"open_issues_count":25,"forks_count":23,"subscribers_count":14,"default_branch":"main","last_synced_at":"2024-05-22T07:51:46.859Z","etag":null,"topics":["android","animation","compose","desktop","gif","image","kotlin","kotlin-library","svg","video"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Kamel-Media.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-01-06T08:14:48.000Z","updated_at":"2024-05-29T22:39:04.164Z","dependencies_parsed_at":"2023-07-26T21:13:15.111Z","dependency_job_id":"fbaa7c12-d370-41cf-ba69-1e63cf10ac7c","html_url":"https://github.com/Kamel-Media/Kamel","commit_stats":null,"previous_names":["alialbaali/kamel"],"tags_count":37,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kamel-Media%2FKamel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kamel-Media%2FKamel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kamel-Media%2FKamel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kamel-Media%2FKamel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Kamel-Media","download_url":"https://codeload.github.com/Kamel-Media/Kamel/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245863100,"owners_count":20684788,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","animation","compose","desktop","gif","image","kotlin","kotlin-library","svg","video"],"created_at":"2024-07-31T17:01:13.568Z","updated_at":"2025-03-27T14:31:28.285Z","avatar_url":"https://github.com/Kamel-Media.png","language":"Kotlin","funding_links":[],"categories":["Kotlin","Libraries"],"sub_categories":["🍎 Compose UI"],"readme":"# Kamel\r\n\r\n[![Version](https://img.shields.io/maven-central/v/media.kamel/kamel-core?label=version\u0026color=blue)](https://search.maven.org/search?q=media.kamel)\r\n[![Snapshot](https://img.shields.io/nexus/s/media.kamel/kamel-core?label=snapshot\u0026server=https%3A%2F%2Fs01.oss.sonatype.org)](https://s01.oss.sonatype.org/content/repositories/snapshots/media/kamel/)\r\n[![License](https://img.shields.io/github/license/alialbaali/kamel)](http://www.apache.org/licenses/LICENSE-2.0)\r\n[![Kotlin](https://img.shields.io/badge/kotlin-v2.1.0-blue.svg?logo=kotlin)](http://kotlinlang.org)\r\n[![Compose Multiplatform](https://img.shields.io/badge/Compose%20Multiplatform-v1.7.3-blue)](https://github.com/JetBrains/compose-multiplatform)\r\n\r\nKamel is an asynchronous media loading library for [Compose Multiplatform](https://github.com/JetBrains/compose-multiplatform). It provides a simple, customizable and\r\nefficient way to load, cache, decode and display images in your application. By default, it uses\r\nKtor client for loading resources.\r\n\r\n## Table of contents\r\n\r\n- [Setup](#setup)\r\n    - [Multi-platform](#multi-platform)\r\n    - [Single-platform](#single-platform)\r\n- [Usage](#usage)\r\n    - [Loading an image resource](#loading-an-image-resource)\r\n        - [Platform specific implementations](#platform-specific-implementations)\r\n            - [Desktop only implementations](#desktop-only-implementations)\r\n            - [Android only implementations](#android-only-implementations)\r\n    - [Configuring an image resource](#configuring-an-image-resource)\r\n    - [Displaying an image resource](#displaying-an-image-resource)\r\n        - [Crossfade animation](#crossfade-animation)\r\n    - [Configuring Kamel](#configuring-kamel)\r\n        - [Memory cache size (number of entries to cache)](#memory-cache-size-number-of-entries-to-cache)\r\n        - [Disk cache size (in bytes)](#disk-cache-size-in-bytes)\r\n    - [Applying Kamel configuration](#applying-kamel-configuration)\r\n- [Contributions](#contributions)\r\n- [License](#license)\r\n\r\n## Setup\r\n\r\nKamel is published on Maven Central:\r\n\r\n```kotlin\r\nrepositories {\r\n    mavenCentral()\r\n    // ...\r\n}\r\n```\r\n\r\n#### Default Setup\r\n\r\nAdd the dependency to the common source-set or to any platform source-set:\r\n\r\n```kotlin\r\nkotlin {\r\n    sourceSets {\r\n        commonMain {\r\n            dependencies {\r\n                implementation(\"media.kamel:kamel-image-default:1.0.3\")\r\n                // no need to specify ktor engines, one is included for each target\r\n                // ...\r\n            }\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n#### Granular Setup\r\n\r\nFor a more granular setup, you can choose which modules to include in your project:\r\n\r\n```kotlin\r\nkotlin {\r\n    sourceSets {\r\n        commonMain {\r\n            dependencies {\r\n                // core module (required)\r\n                implementation(\"media.kamel:kamel-image:1.0.3\")\r\n                \r\n                // Note: When using `kamel-image` a ktor engine is not included.\r\n                // To fetch remote images you also must ensure you add your own \r\n                // ktor engine for each target.\r\n                \r\n                // optional modules (choose what you need and add them to your kamel config)\r\n                implementation(\"media.kamel:kamel-decoder-image-bitmap:1.0.3\")\r\n                implementation(\"media.kamel:kamel-decoder-image-bitmap-resizing:1.0.3\") // android only right now\r\n                implementation(\"media.kamel:kamel-decoder-image-vector:1.0.3\")\r\n                implementation(\"media.kamel:kamel-decoder-svg-batik:1.0.3\")\r\n                implementation(\"media.kamel:kamel-decoder-svg-std:1.0.3\")\r\n                implementation(\"media.kamel:kamel-decoder-animated-image:1.0.3\")\r\n\r\n                implementation(\"media.kamel:kamel-fetcher-resources-jvm:1.0.3\")\r\n                implementation(\"media.kamel:kamel-fetcher-resources-android:1.0.3\")\r\n                // ...\r\n            }\r\n        }\r\n\r\n        jvmMain {\r\n            dependencies {\r\n                // optional modules (choose what you need and add them to your kamel config)\r\n                implementation(\"media.kamel:kamel-fetcher-resources-jvm:1.0.3\")\r\n                // ...\r\n            }\r\n        }\r\n\r\n        androidMain {\r\n            dependencies {\r\n                // optional modules (choose what you need and add them to your kamel config)\r\n                implementation(\"media.kamel:kamel-fetcher-resources-android:1.0.3\")\r\n                // ...\r\n            }\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n##### Granular Setup: Ktor HttpClient Engine \r\n\r\nWhen using `kamel-image` ktor engines are not included per target.\r\nIn order to fetch remote images you also must ensure you add your own ktor engine for each target.\r\nYou can find the available engines [here](https://ktor.io/docs/http-client-engines.html).\r\n\r\n## Usage\r\n\r\n### Loading an image resource\r\n\r\nTo load an image asynchronously, you can use ```asyncPainterResource``` composable, it can load\r\nimages from different data sources:\r\n\r\n```kotlin\r\n// String\r\nasyncPainterResource(data = \"https://www.example.com/image.jpg\")\r\nasyncPainterResource(data = \"file:///path/to/image.png\")\r\n\r\n// Ktor Url\r\nasyncPainterResource(data = Url(\"https://www.example.com/image.jpg\"))\r\n\r\n// URI\r\nasyncPainterResource(data = URI(\"https://www.example.com/image.png\"))\r\n\r\n// File (JVM, Native)\r\nasyncPainterResource(data = File(\"/path/to/image.png\"))\r\n\r\n// File (JS)\r\nasyncPainterResource(data = File(org.w3c.files.File(arrayOf(blob), \"/path/to/image.png\")))\r\n\r\n// URL\r\nasyncPainterResource(data = URL(\"https://www.example.com/image.jpg\"))\r\n\r\n// and more...\r\n```\r\n\r\n`asyncPainterResource` can be used to load SVG, XML, JPEG, and PNG by default depending on the\r\nplatform implementation.\r\n\r\n`asyncPainterResource` returns a `Resource\u003cPainter\u003e` object which can be used to display the image\r\nusing `KamelImage` composable.\r\n\r\n#### Platform specific implementations\r\n\r\nSince there isn't any shared resource system between Android and Desktop, some implementations (e.g.\r\nfetchers and mappers) are only available for a specific platform:\r\n\r\n#### Desktop only implementations\r\n\r\nTo load an image file from desktop application resources, you have to add `resourcesFetcher` to\r\nthe `KamelConfig`:\r\n\r\n```kotlin\r\nval desktopConfig = KamelConfig {\r\n    takeFrom(KamelConfig.Default)\r\n    // Available only on Desktop.\r\n    resourcesFetcher()\r\n    // Available only on Desktop.\r\n    // An alternative svg decoder\r\n    batikSvgDecoder()\r\n}\r\n```\r\n\r\nAssuming there's an `image.png` file in the `/resources` directory in the project:\r\n\r\n```kotlin\r\nCompositionLocalProvider(LocalKamelConfig provides desktopConfig) {\r\n    asyncPainterResource(\"image.png\")\r\n}\r\n```\r\n\r\n#### Android only implementations\r\n\r\nTo load an image file from android application resources, you have to add `resourcesFetcher`\r\nand `resourcesIdMapper` to the `KamelConfig`:\r\n\r\n```kotlin\r\nval context: Context = LocalContext.current\r\n\r\nval androidConfig = KamelConfig {\r\n    takeFrom(KamelConfig.Default)\r\n    // Available only on Android.\r\n    resourcesFetcher(context)\r\n    // Available only on Android.\r\n    resourcesIdMapper(context)\r\n}\r\n```\r\n\r\nAssuming there's an `image.png` file in the `/res/raw` directory in the project:\r\n\r\n```kotlin\r\nCompositionLocalProvider(LocalKamelConfig provides androidConfig) {\r\n    asyncPainterResource(R.raw.image)\r\n}\r\n```\r\n\r\n### Configuring an image resource\r\n\r\n```asyncPainterResource``` supports configuration using a trailing lambda:\r\n\r\n```kotlin\r\nval painterResource: Resource\u003cPainter\u003e = asyncPainterResource(\"https://www.example.com/image.jpg\") {\r\n\r\n    // CoroutineContext to be used while loading the image.\r\n    coroutineContext = Job() + Dispatcher.IO\r\n\r\n    // Customizes HTTP request\r\n    requestBuilder { // this: HttpRequestBuilder\r\n        header(\"Key\", \"Value\")\r\n        parameter(\"Key\", \"Value\")\r\n        cacheControl(CacheControl.MAX_AGE)\r\n    }\r\n\r\n}\r\n```\r\n\r\n### Displaying an image resource\r\n\r\n```KamelImage``` is a composable function that takes a ```Resource\u003cPainter\u003e``` object, display it\r\nand provide extra functionality:\r\n\r\n```kotlin\r\nKamelImage(\r\n    resource = painterResource,\r\n    contentDescription = \"Profile\",\r\n)\r\n```\r\n\r\n```KamelImage``` can also be used to get the ```exception``` using ```onFailure```,\r\nand ```progress``` using ```onLoading``` parameters, to display a snackbar or a progress indicator,\r\ndepending on the case:\r\n\r\n```kotlin\r\nval coroutineScope = rememberCoroutineScope()\r\nval snackbarHostState = remember { SnackbarHostState() }\r\nBox {\r\n    KamelImage(\r\n        resource = painterResource,\r\n        contentDescription = \"Profile\",\r\n        onLoading = { progress -\u003e CircularProgressIndicator(progress) },\r\n        onFailure = { exception -\u003e\r\n            coroutineScope.launch {\r\n                snackbarHostState.showSnackbar(\r\n                    message = exception.message.toString(),\r\n                    actionLabel = \"Hide\",\r\n                    duration = SnackbarDuration.Short\r\n                )\r\n            }\r\n        }\r\n    )\r\n    SnackbarHost(hostState = snackbarHostState, modifier = Modifier.padding(16.dp))\r\n}\r\n```\r\n\r\nYou can also provide your own custom implementation using a simple when expression:\r\n\r\n```kotlin\r\nwhen (val resource = asyncPainterResource(\"https://www.example.com/image.jpg\")) {\r\n    is Resource.Loading -\u003e {\r\n        Text(\"Loading...\")\r\n    }\r\n    is Resource.Success -\u003e {\r\n        val painter: Painter = resource.value\r\n        Image(painter, contentDescription = \"Profile\")\r\n    }\r\n    is Resource.Failure -\u003e {\r\n        log(resource.exception)\r\n        val fallbackPainter = painterResource(\"/path/to/fallbackImage.jpg\")\r\n        Image(fallbackPainter, contentDescription = \"Profile\")\r\n    }\r\n}\r\n```\r\n\r\n#### Crossfade animation\r\n\r\nYou can enable, disable or customize crossfade (fade-in) animation through the ```animationSpec```\r\nparameter. Setting ```animationSpec``` to `null` will disable the animation:\r\n\r\n```kotlin\r\nKamelImage(\r\n    resource = imageResource,\r\n    contentDescription = \"Profile\",\r\n    // null by default\r\n    animationSpec = tween(),\r\n)\r\n```\r\n\r\n### Configuring Kamel\r\n\r\nThe default implementation is ```KamelConfig.Default```. If you wish to configure it, you can do it\r\nthe following way:\r\n\r\n```kotlin\r\nval customKamelConfig = KamelConfig {\r\n    // Copies the default implementation if needed\r\n    takeFrom(KamelConfig.Default)\r\n\r\n    // Sets the number of images to cache\r\n    imageBitmapCacheSize = DefaultCacheSize\r\n\r\n    // adds an ImageBitmapDecoder\r\n    imageBitmapDecoder()\r\n\r\n    // adds an ImageVectorDecoder (XML)\r\n    imageVectorDecoder()\r\n\r\n    // adds an SvgDecoder (SVG)\r\n    svgDecoder()\r\n\r\n    // adds a FileFetcher\r\n    fileFetcher()\r\n\r\n    \r\n    // Configures Ktor HttpClient\r\n    httpUrlFetcher {\r\n        // httpCache is defined in kamel-core and configures the ktor client \r\n        // to install a HttpCache feature with the implementation provided by Kamel.\r\n        // The size of the cache can be defined in Bytes.\r\n        httpCache(10 * 1024 * 1024  /* 10 MiB */)\r\n\r\n        defaultRequest {\r\n            url(\"https://www.example.com/\")\r\n            cacheControl(CacheControl.MaxAge(maxAgeSeconds = 10000))\r\n        }\r\n\r\n        install(HttpRequestRetry) {\r\n            maxRetries = 3\r\n            retryIf { httpRequest, httpResponse -\u003e\r\n                !httpResponse.status.isSuccess()\r\n            }\r\n        }\r\n\r\n        // Requires adding \"io.ktor:ktor-client-logging:$ktor_version\"\r\n        Logging {\r\n            level = LogLevel.INFO\r\n            logger = Logger.SIMPLE\r\n        }\r\n    }\r\n\r\n    // more functionality available.\r\n}\r\n\r\n```\r\n\r\n#### Memory cache size (number of entries to cache)\r\n\r\nKamel provides a generic `Cache\u003cK,V\u003e` interface, the default implementation uses LRU memory cache\r\nmechanism backed by `LinkedHashMap`. You can provide a number of entries to cache for each type like\r\nso:\r\n\r\n```kotlin\r\nKamelConfig {\r\n    // 100 by default\r\n    imageBitmapCacheSize = 500\r\n    // 100 by default\r\n    imageVectorCacheSize = 300\r\n    // 100 by default\r\n    svgCacheSize = 200\r\n}\r\n```\r\n\r\n#### Disk cache size (in bytes)\r\n\r\nKamel can create a persistent disk cache for images by implementing ktor's `CacheStorage` feature.\r\nThe default config `KamelConfig.Default` installs this feature with a 10 MiB disk cache size.\r\nThe underlying disk cache is based on coil's multiplatform `DiskLruCache` implementation.\r\n\r\n```kotlin\r\nKamelConfig {\r\n    httpFetcher {\r\n        // The size of the cache can be defined in bytes. Or DefaultHttpCacheSize (10 MiB) can be used. \r\n        httpCache(DefaultHttpCacheSize)\r\n    }\r\n}\r\n```\r\n\r\n### Applying Kamel configuration\r\n\r\nYou can use ```LocalKamelConfig``` to apply your custom configuration:\r\n\r\n```kotlin\r\nCompositionLocalProvider(LocalKamelConfig provides customKamelConfig) {\r\n    asyncPainterResource(\"image.jpg\")\r\n}\r\n```\r\n\r\n## Contributions\r\n\r\nContributions are always welcome!. If you'd like to contribute, please feel free to create a PR or\r\nopen an issue.\r\n\r\n## License\r\n\r\n```\r\nCopyright 2021 Ali Albaali\r\n\r\nLicensed under the Apache License, Version 2.0 (the \"License\");\r\nyou may not use this file except in compliance with the License.\r\nYou may obtain a copy of the License at\r\n\r\n   https://www.apache.org/licenses/LICENSE-2.0\r\n\r\nUnless required by applicable law or agreed to in writing, software\r\ndistributed under the License is distributed on an \"AS IS\" BASIS,\r\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\nSee the License for the specific language governing permissions and\r\nlimitations under the License.\r\n```\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FKamel-Media%2FKamel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FKamel-Media%2FKamel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FKamel-Media%2FKamel/lists"}