
Library enables mesmerizing shader backgrounds in Compose-based UIs, offering a `Modifier` for seamless integration. Includes various shaders with different licenses, enhancing visual appeal in user interfaces.
Animated shader backgrounds for Compose Multiplatform.
Quickstart • Showcase • Reference • Live demo • Security
| Capability | What you get |
|---|---|
| Compose modifier | Apply a shader behind your existing UI with Modifier.shaderBackground. |
| Configurable animation | Set animation speed; configure MeshGradient colors and scale. |
| Android fallback | Supply a Brush on Android below API 33. The default fallback is transparent. |
| Multiplatform | Android, Desktop JVM, iOS, and Wasm sample apps. |
| Separate shader modules | Three MIT core shaders; additional shaders have separate license terms. |
commonMain. Use the version shown by the Maven Central badge:commonMain.dependencies {
implementation("com.mikepenz.hypnoticcanvas:hypnoticcanvas:<version>")
}import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
import com.mikepenz.hypnoticcanvas.shaderBackground
import com.mikepenz.hypnoticcanvas.shaders.GlossyGradients
Box(Modifier.fillMaxSize().shaderBackground(GlossyGradients))import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
Box(
Modifier.fillMaxSize().shaderBackground(
shader = GlossyGradients,
speed = 0.5f,
fallback = { Brush.horizontalGradient(listOf(Color.Magenta, Color.Blue)) },
)
)[!IMPORTANT] The optional
hypnoticcanvas-shadersmodule includes noncommercial shaders. Read the shader credits and license terms before using it.
These are fixed-time renders of the actual core shaders using the same Skia runtime
and Compose brush wrapper as the Desktop sample. They show shader output, without sample UI overlays.
The MeshGradient panel uses the sample app's colors and scale = 1f.
Source: ReadmeArtTest.kt.
MeshGradient • MesmerizingLens • GlossyGradients. All three core shaders are MIT licensed.
PrismGlass refracts an ImageBitmap with a chromatic edge and a moving glass
panel. SpectralAurora uses raw waveform and spectrum textures to shape and light
its ribbons, with a Compose gradient bound as a nested shader for the palette.
Both are MIT licensed and live in hypnoticcanvas-shaders. Their constructors
provide generated demo inputs; replace them with your own image or packed RGB
audio data. The sample starts with Spectral Aurora, followed by Prism Glass.
Modifier.shaderBackground(PrismGlass()) // Or PrismGlass(imageBitmap)
Modifier.shaderBackground(SpectralAurora()) // Generated waveform and spectrum
Modifier.shaderBackground(SpectralAurora(waveformRgb, spectrumRgb))For SpectralAurora, each RGB triplet's red byte contains a sample. Encode signed
waveform values into 0–255, and normalize spectrum magnitudes into the same range.
The arrays must be nonempty and divisible by three. Changed array contents are
uploaded on the next draw. See Texture inputs for the encoding.
Refresh images deliberately, then verify them:
./gradlew :hypnoticcanvas-shaders:recordReadmeArt -PrecordReadmeArt=true --rerun-tasks
./gradlew :hypnoticcanvas-shaders:verifyReadmeArtThe original recorded demo is also available:
https://github.com/mikepenz/HypnoticCanvas/assets/1476232/ee120f1c-d18a-43c4-a7bc-a2d245e01482
| Topic | Link |
|---|---|
| Dependencies | Setup |
| Shader configuration | Usage |
| Images and audio data | Texture inputs |
| Platform requirements | Compatibility |
| Sample commands | Build and run |
| Authors and shader licenses | Credit |
| License terms | License |
| Vulnerability reporting | Security policy |
implementation "com.mikepenz.hypnoticcanvas:hypnoticcanvas:${version}"[!NOTE]
All shaders provided in thecoremodule are licensed either underMITorApache 2.0license.
implementation "com.mikepenz.hypnoticcanvas:hypnoticcanvas-shaders:${version}"[!IMPORTANT]
Shaders in this module have non permissive licenses. Ensure to read the LICENSE section of the README.
Box(
modifier = Modifier
.fillMaxSize()
.shaderBackground(BlackCherryCosmos)
)
// Usage of Shader with configurations
Box(
modifier = Modifier
.fillMaxSize()
.shaderBackground(
MeshGradient(
arrayOf(Color(0xFFFF15E5), Color(0xFFFAAEF7), Color(0xFF6903F9)),
scale = 1f
)
)
)Bind inputs from your shader's applyUniforms implementation. Use
setShaderUniform(name, shader) for a Compose graphics shader, or
setTextureUniform(name, imageBitmap) for an image. Image inputs use normal
color-space and alpha handling and default to clamp addressing. Optional
tileModeX and tileModeY arguments control tiling.
For waveform or FFT data, setDataTextureUniform(name, width, height, rgb) uploads
a snapshot of packed RGB bytes, in row order. Each unsigned channel is sampled as
value / 255, with alpha fixed at 1, nearest filtering, and clamp addressing.
There is no color conversion. The array must contain exactly width * height * 3
bytes, and both dimensions must be positive. Rebind to upload changed samples.
import com.mikepenz.hypnoticcanvas.RuntimeEffect
import com.mikepenz.hypnoticcanvas.shaders.GlossyGradients
import com.mikepenz.hypnoticcanvas.shaders.Shader
class AudioTextureShader(
private val waveformRgb: ByteArray,
private val fftRgb: ByteArray,
) : Shader by GlossyGradients {
override val name = "Audio textures"
override val sksl = """
uniform float3 uResolution;
uniform shader waveform;
uniform shader fft;
uniform float2 sampleCounts;
half4 main(float2 p) {
float x = p.x / uResolution.x;
float wave = waveform.eval(float2(x * sampleCounts.x, 0.5)).r;
float magnitude = fft.eval(float2(x * sampleCounts.y, 0.5)).r;
float line = 1.0 - step(0.02, abs(p.y / uResolution.y - wave));
return half4(line, magnitude, 0.0, 1.0);
}
""".trimIndent()
override fun applyUniforms(effect: RuntimeEffect, time: Float, width: Float, height: Float) {
effect.setFloatUniform("uResolution", width, height, width / height)
effect.setFloatUniform("sampleCounts", waveformRgb.size / 3f, fftRgb.size / 3f)
effect.setDataTextureUniform("waveform", waveformRgb.size / 3, 1, waveformRgb)
effect.setDataTextureUniform("fft", fftRgb.size / 3, 1, fftRgb)
}
}Store samples in the red byte of each RGB triplet; unused green and blue bytes
can be zero. Encode signed waveform values in −1…1 as
((sample.coerceIn(-1f, 1f) + 1f) * 127.5f).roundToInt().toByte().
Decode with sample.r * 2.0 - 1.0 if the shader needs the signed value. Kotlin
bytes above 127 appear negative; their bits still represent unsigned samples.
FFT magnitudes need normalization into 0…1 before encoding into 0…255.
SkSL/AGSL uses uniform shader and .eval() instead of GLSL sampler2D and
texture(). Coordinates are in texture pixels, with texel centers at x + 0.5
and y + 0.5. All inputs must be bound before rendering. Input names must match
the shader declarations; Android rejects unknown names, while Skiko ignores them.
Android below API 33 keeps using the fallback brush.
Raw uploads allocate a new texture on each call; this example uploads on every
frame. Audio capture and FFT calculation remain the caller's responsibility.
This checkout uses Kotlin 2.4.10 and Compose Multiplatform 1.12.0. Android requires API 23 or newer, with shaders available on API 33 or newer and the fallback brush used below API 33. iOS requires version 14 or newer. Building requires JDK 21.
HypnoticCanvas is built with Compose Multiplatform, meaning that it supports different platforms:
| Platform | Supported | Link |
|---|---|---|
| Android | ✅ (A13+) | |
| Desktop (JVM) | ✅ | |
| iOS | ✅ | |
| Wasm | ✅ | Sample |
./gradlew sample-android:assembleDebug./gradlew sample:run./gradlew sample:wasmJsBrowserDevelopmentRun ./gradlew sample:exportLibraryDefinitionsThe base project setup is strongly based on the haze project by Chris Banes, Licensed under Apache License 2.0
The individual shaders are based on the respective shaders licenses. More details below.
| Name | Author | License | Note |
|---|---|---|---|
| MeshGradient | Mike Penz | MIT License | |
| MesmerizingLens | Mike Penz | MIT License | |
| GlossyGradients | Giorgi Azmaipharashvili | MIT License | Rights bought for this shader on Fiverr, included in this project under MIT. |
| Name | Author | License | Note |
|---|---|---|---|
| Prism Glass | MIT License | Original image-input shader. | |
| Spectral Aurora | MIT License | Original waveform, spectrum, and palette-input shader. | |
| BlackCherryCosmos2 via via | axiomcrux | CC BY-NC-SA 3.0 DEED | Shader does not specifically include license, however found the shader it appears to be based on. |
| GoldenMagma | TAKUSAKU | CC BY-NC-SA 3.0 DEED | |
| IceReflection | TAKUSAKU | CC BY-NC-SA 3.0 DEED | |
| InkFlow | TAKUSAKU | CC BY-NC-SA 3.0 DEED | |
| OilFlow | TAKUSAKU | CC BY-NC-SA 3.0 DEED | |
| PurpleLiquid | fouad | CC BY-NC-SA 3.0 DEED | |
| RainbowWater | flylo | CC BY-NC-SA 3.0 DEED | |
| Stage | TAKUSAKU | CC BY-NC-SA 3.0 DEED |
The core project code in this repository is licensed as under Apache
2.0. SPDX-License-Identifier: Apache-2.0.
All Shaders are provided under their respective Authors license.
Shaders in the hypnoticcanvas module are licensed either as MIT, or Apache 2.0.
Prism Glass and Spectral Aurora in hypnoticcanvas-shaders are MIT licensed.
The other shaders in that module retain their individual licenses, including
SPDX-License-Identifier: CC-BY-NC-SA-3.0; see the credits above.
The source code for the core module is licensed under Apache 2.0, with the shaders provided in the core module as MIT License.
Copyright 2024-2026 Mike Penz
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Prism Glass and Spectral Aurora are original shaders covered by the scoped MIT notice. The same artifact also includes the shaders below with their existing license terms.
Shaders in this module are most from ShaderToy.com and are licensed Attribution-NonCommercial-ShareAlike 3.0 Unported. Which is the default license as outlined by ShaderToy.com
Animated shader backgrounds for Compose Multiplatform.
Quickstart • Showcase • Reference • Live demo • Security
| Capability | What you get |
|---|---|
| Compose modifier | Apply a shader behind your existing UI with Modifier.shaderBackground. |
| Configurable animation | Set animation speed; configure MeshGradient colors and scale. |
| Android fallback | Supply a Brush on Android below API 33. The default fallback is transparent. |
| Multiplatform | Android, Desktop JVM, iOS, and Wasm sample apps. |
| Separate shader modules | Three MIT core shaders; additional shaders have separate license terms. |
commonMain. Use the version shown by the Maven Central badge:commonMain.dependencies {
implementation("com.mikepenz.hypnoticcanvas:hypnoticcanvas:<version>")
}import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
import com.mikepenz.hypnoticcanvas.shaderBackground
import com.mikepenz.hypnoticcanvas.shaders.GlossyGradients
Box(Modifier.fillMaxSize().shaderBackground(GlossyGradients))import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
Box(
Modifier.fillMaxSize().shaderBackground(
shader = GlossyGradients,
speed = 0.5f,
fallback = { Brush.horizontalGradient(listOf(Color.Magenta, Color.Blue)) },
)
)[!IMPORTANT] The optional
hypnoticcanvas-shadersmodule includes noncommercial shaders. Read the shader credits and license terms before using it.
These are fixed-time renders of the actual core shaders using the same Skia runtime
and Compose brush wrapper as the Desktop sample. They show shader output, without sample UI overlays.
The MeshGradient panel uses the sample app's colors and scale = 1f.
Source: ReadmeArtTest.kt.
MeshGradient • MesmerizingLens • GlossyGradients. All three core shaders are MIT licensed.
PrismGlass refracts an ImageBitmap with a chromatic edge and a moving glass
panel. SpectralAurora uses raw waveform and spectrum textures to shape and light
its ribbons, with a Compose gradient bound as a nested shader for the palette.
Both are MIT licensed and live in hypnoticcanvas-shaders. Their constructors
provide generated demo inputs; replace them with your own image or packed RGB
audio data. The sample starts with Spectral Aurora, followed by Prism Glass.
Modifier.shaderBackground(PrismGlass()) // Or PrismGlass(imageBitmap)
Modifier.shaderBackground(SpectralAurora()) // Generated waveform and spectrum
Modifier.shaderBackground(SpectralAurora(waveformRgb, spectrumRgb))For SpectralAurora, each RGB triplet's red byte contains a sample. Encode signed
waveform values into 0–255, and normalize spectrum magnitudes into the same range.
The arrays must be nonempty and divisible by three. Changed array contents are
uploaded on the next draw. See Texture inputs for the encoding.
Refresh images deliberately, then verify them:
./gradlew :hypnoticcanvas-shaders:recordReadmeArt -PrecordReadmeArt=true --rerun-tasks
./gradlew :hypnoticcanvas-shaders:verifyReadmeArtThe original recorded demo is also available:
https://github.com/mikepenz/HypnoticCanvas/assets/1476232/ee120f1c-d18a-43c4-a7bc-a2d245e01482
| Topic | Link |
|---|---|
| Dependencies | Setup |
| Shader configuration | Usage |
| Images and audio data | Texture inputs |
| Platform requirements | Compatibility |
| Sample commands | Build and run |
| Authors and shader licenses | Credit |
| License terms | License |
| Vulnerability reporting | Security policy |
implementation "com.mikepenz.hypnoticcanvas:hypnoticcanvas:${version}"[!NOTE]
All shaders provided in thecoremodule are licensed either underMITorApache 2.0license.
implementation "com.mikepenz.hypnoticcanvas:hypnoticcanvas-shaders:${version}"[!IMPORTANT]
Shaders in this module have non permissive licenses. Ensure to read the LICENSE section of the README.
Box(
modifier = Modifier
.fillMaxSize()
.shaderBackground(BlackCherryCosmos)
)
// Usage of Shader with configurations
Box(
modifier = Modifier
.fillMaxSize()
.shaderBackground(
MeshGradient(
arrayOf(Color(0xFFFF15E5), Color(0xFFFAAEF7), Color(0xFF6903F9)),
scale = 1f
)
)
)Bind inputs from your shader's applyUniforms implementation. Use
setShaderUniform(name, shader) for a Compose graphics shader, or
setTextureUniform(name, imageBitmap) for an image. Image inputs use normal
color-space and alpha handling and default to clamp addressing. Optional
tileModeX and tileModeY arguments control tiling.
For waveform or FFT data, setDataTextureUniform(name, width, height, rgb) uploads
a snapshot of packed RGB bytes, in row order. Each unsigned channel is sampled as
value / 255, with alpha fixed at 1, nearest filtering, and clamp addressing.
There is no color conversion. The array must contain exactly width * height * 3
bytes, and both dimensions must be positive. Rebind to upload changed samples.
import com.mikepenz.hypnoticcanvas.RuntimeEffect
import com.mikepenz.hypnoticcanvas.shaders.GlossyGradients
import com.mikepenz.hypnoticcanvas.shaders.Shader
class AudioTextureShader(
private val waveformRgb: ByteArray,
private val fftRgb: ByteArray,
) : Shader by GlossyGradients {
override val name = "Audio textures"
override val sksl = """
uniform float3 uResolution;
uniform shader waveform;
uniform shader fft;
uniform float2 sampleCounts;
half4 main(float2 p) {
float x = p.x / uResolution.x;
float wave = waveform.eval(float2(x * sampleCounts.x, 0.5)).r;
float magnitude = fft.eval(float2(x * sampleCounts.y, 0.5)).r;
float line = 1.0 - step(0.02, abs(p.y / uResolution.y - wave));
return half4(line, magnitude, 0.0, 1.0);
}
""".trimIndent()
override fun applyUniforms(effect: RuntimeEffect, time: Float, width: Float, height: Float) {
effect.setFloatUniform("uResolution", width, height, width / height)
effect.setFloatUniform("sampleCounts", waveformRgb.size / 3f, fftRgb.size / 3f)
effect.setDataTextureUniform("waveform", waveformRgb.size / 3, 1, waveformRgb)
effect.setDataTextureUniform("fft", fftRgb.size / 3, 1, fftRgb)
}
}Store samples in the red byte of each RGB triplet; unused green and blue bytes
can be zero. Encode signed waveform values in −1…1 as
((sample.coerceIn(-1f, 1f) + 1f) * 127.5f).roundToInt().toByte().
Decode with sample.r * 2.0 - 1.0 if the shader needs the signed value. Kotlin
bytes above 127 appear negative; their bits still represent unsigned samples.
FFT magnitudes need normalization into 0…1 before encoding into 0…255.
SkSL/AGSL uses uniform shader and .eval() instead of GLSL sampler2D and
texture(). Coordinates are in texture pixels, with texel centers at x + 0.5
and y + 0.5. All inputs must be bound before rendering. Input names must match
the shader declarations; Android rejects unknown names, while Skiko ignores them.
Android below API 33 keeps using the fallback brush.
Raw uploads allocate a new texture on each call; this example uploads on every
frame. Audio capture and FFT calculation remain the caller's responsibility.
This checkout uses Kotlin 2.4.10 and Compose Multiplatform 1.12.0. Android requires API 23 or newer, with shaders available on API 33 or newer and the fallback brush used below API 33. iOS requires version 14 or newer. Building requires JDK 21.
HypnoticCanvas is built with Compose Multiplatform, meaning that it supports different platforms:
| Platform | Supported | Link |
|---|---|---|
| Android | ✅ (A13+) | |
| Desktop (JVM) | ✅ | |
| iOS | ✅ | |
| Wasm | ✅ | Sample |
./gradlew sample-android:assembleDebug./gradlew sample:run./gradlew sample:wasmJsBrowserDevelopmentRun ./gradlew sample:exportLibraryDefinitionsThe base project setup is strongly based on the haze project by Chris Banes, Licensed under Apache License 2.0
The individual shaders are based on the respective shaders licenses. More details below.
| Name | Author | License | Note |
|---|---|---|---|
| MeshGradient | Mike Penz | MIT License | |
| MesmerizingLens | Mike Penz | MIT License | |
| GlossyGradients | Giorgi Azmaipharashvili | MIT License | Rights bought for this shader on Fiverr, included in this project under MIT. |
| Name | Author | License | Note |
|---|---|---|---|
| Prism Glass | MIT License | Original image-input shader. | |
| Spectral Aurora | MIT License | Original waveform, spectrum, and palette-input shader. | |
| BlackCherryCosmos2 via via | axiomcrux | CC BY-NC-SA 3.0 DEED | Shader does not specifically include license, however found the shader it appears to be based on. |
| GoldenMagma | TAKUSAKU | CC BY-NC-SA 3.0 DEED | |
| IceReflection | TAKUSAKU | CC BY-NC-SA 3.0 DEED | |
| InkFlow | TAKUSAKU | CC BY-NC-SA 3.0 DEED | |
| OilFlow | TAKUSAKU | CC BY-NC-SA 3.0 DEED | |
| PurpleLiquid | fouad | CC BY-NC-SA 3.0 DEED | |
| RainbowWater | flylo | CC BY-NC-SA 3.0 DEED | |
| Stage | TAKUSAKU | CC BY-NC-SA 3.0 DEED |
The core project code in this repository is licensed as under Apache
2.0. SPDX-License-Identifier: Apache-2.0.
All Shaders are provided under their respective Authors license.
Shaders in the hypnoticcanvas module are licensed either as MIT, or Apache 2.0.
Prism Glass and Spectral Aurora in hypnoticcanvas-shaders are MIT licensed.
The other shaders in that module retain their individual licenses, including
SPDX-License-Identifier: CC-BY-NC-SA-3.0; see the credits above.
The source code for the core module is licensed under Apache 2.0, with the shaders provided in the core module as MIT License.
Copyright 2024-2026 Mike Penz
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Prism Glass and Spectral Aurora are original shaders covered by the scoped MIT notice. The same artifact also includes the shaders below with their existing license terms.
Shaders in this module are most from ShaderToy.com and are licensed Attribution-NonCommercial-ShareAlike 3.0 Unported. Which is the default license as outlined by ShaderToy.com