Roman Sedaikin
4 years ago
6 changed files with 351 additions and 0 deletions
After Width: | Height: | Size: 459 KiB |
After Width: | Height: | Size: 317 KiB |
@ -0,0 +1,351 @@
|
||||
# Image and in-app icons manipulations |
||||
|
||||
## What is covered |
||||
|
||||
In this tutorial we will show you how to work with images using Compose for Desktop. |
||||
|
||||
## Loading images from resources |
||||
|
||||
Using images from application resources is very simple. Suppose we have a PNG image that is placed in the `resources/images` directory in our project. For this tutorial we will use the image sample: |
||||
|
||||
<img src="sample.png" alt="Sample" width="256"/> |
||||
|
||||
```kotlin |
||||
import androidx.compose.desktop.Window |
||||
import androidx.compose.foundation.Image |
||||
import androidx.compose.foundation.layout.fillMaxSize |
||||
import androidx.compose.ui.Modifier |
||||
import androidx.compose.ui.res.imageResource |
||||
|
||||
fun main() { |
||||
Window { |
||||
Image( |
||||
asset = imageResource("images/sample.png"), // ImageAsset |
||||
modifier = Modifier.fillMaxSize() |
||||
) |
||||
} |
||||
} |
||||
``` |
||||
|
||||
![Resources](image_from_resources.png) |
||||
|
||||
## Loading images from device storage |
||||
|
||||
To create `ImageAsset` from a loaded image stored in device memory you can use skija `Image`: |
||||
|
||||
```kotlin |
||||
import androidx.compose.desktop.Window |
||||
import androidx.compose.foundation.Image |
||||
import androidx.compose.foundation.layout.fillMaxSize |
||||
import androidx.compose.ui.graphics.asImageAsset |
||||
import androidx.compose.ui.graphics.ImageAsset |
||||
import androidx.compose.ui.Modifier |
||||
import java.awt.image.BufferedImage |
||||
import java.io.ByteArrayOutputStream |
||||
import java.io.File |
||||
import javax.imageio.ImageIO |
||||
import org.jetbrains.skija.Image |
||||
|
||||
fun main() { |
||||
Window { |
||||
val image = pathAsImageAsset("sample.png") |
||||
Image( |
||||
asset = image, |
||||
modifier = Modifier.fillMaxSize() |
||||
) |
||||
} |
||||
} |
||||
|
||||
fun pathAsImageAsset(path: String): ImageAsset { |
||||
var image: BufferedImage? = null |
||||
try { |
||||
image = ImageIO.read(File(path)) |
||||
} catch (e: Exception) { |
||||
// image file does not exist |
||||
} |
||||
|
||||
if (image == null) { |
||||
image = BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB) |
||||
} |
||||
|
||||
val baos = ByteArrayOutputStream() |
||||
ImageIO.write(image, "png", baos) |
||||
|
||||
return Image.makeFromEncoded(baos.toByteArray()).asImageAsset() |
||||
} |
||||
``` |
||||
|
||||
![Storage](image_from_resources.png) |
||||
|
||||
## Drawing raw images |
||||
|
||||
Sometimes you may want to draw raw image data, in which case you can use `Canvas` and` drawIntoCanvas`. |
||||
|
||||
```kotlin |
||||
import androidx.compose.desktop.Window |
||||
import androidx.compose.foundation.Canvas |
||||
import androidx.compose.foundation.layout.fillMaxSize |
||||
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas |
||||
import androidx.compose.ui.graphics.nativeCanvas |
||||
import androidx.compose.ui.Modifier |
||||
import java.awt.image.BufferedImage |
||||
import java.io.ByteArrayOutputStream |
||||
import java.io.File |
||||
import javax.imageio.ImageIO |
||||
import org.jetbrains.skija.ColorAlphaType |
||||
import org.jetbrains.skija.Bitmap |
||||
import org.jetbrains.skija.ImageInfo |
||||
import org.jetbrains.skija.IRect |
||||
|
||||
fun main() { |
||||
Window { |
||||
val bitmap = bitmapFromByteArray() |
||||
Canvas( |
||||
modifier = Modifier.fillMaxSize() |
||||
) { |
||||
drawIntoCanvas { canvas -> |
||||
canvas.nativeCanvas.drawBitmapRect( |
||||
bitmap, |
||||
IRect(0, 0, bitmap.getWidth(), bitmap.getHeight()).toRect() |
||||
) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
fun bitmapFromByteArray(): Bitmap { |
||||
var image: BufferedImage? = null |
||||
try { |
||||
image = ImageIO.read(File("sample.png")) |
||||
} catch (e: Exception) { |
||||
// image file does not exist |
||||
} |
||||
|
||||
if (image == null) { |
||||
image = BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB) |
||||
} |
||||
val pixels = getBytes(image) // assume we only have raw pixels |
||||
|
||||
// allocate and fill skija Bitmap |
||||
val bitmap = Bitmap() |
||||
bitmap.allocPixels(ImageInfo.makeS32(image.width, image.height, ColorAlphaType.PREMUL)) |
||||
bitmap.installPixels(bitmap.getImageInfo(), pixels, (image.width * 4).toLong()) |
||||
|
||||
return bitmap |
||||
} |
||||
|
||||
// creating byte array from BufferedImage |
||||
private fun getBytes(image: BufferedImage): ByteArray { |
||||
val width = image.width |
||||
val height = image.height |
||||
|
||||
val buffer = IntArray(width * height) |
||||
image.getRGB(0, 0, width, height, buffer, 0, width) |
||||
|
||||
val pixels = ByteArray(width * height * 4) |
||||
|
||||
var index = 0 |
||||
for (y in 0 until height) { |
||||
for (x in 0 until width) { |
||||
val pixel = buffer[y * width + x] |
||||
pixels[index++] = ((pixel and 0xFF)).toByte() // Blue component |
||||
pixels[index++] = (((pixel shr 8) and 0xFF)).toByte() // Green component |
||||
pixels[index++] = (((pixel shr 16) and 0xFF)).toByte() // Red component |
||||
pixels[index++] = (((pixel shr 24) and 0xFF)).toByte() // Alpha component |
||||
} |
||||
} |
||||
|
||||
return pixels |
||||
} |
||||
``` |
||||
|
||||
![Drawing raw images](draw_image_into_canvas.png) |
||||
|
||||
## Setting the application window icon |
||||
|
||||
You have 2 ways to set icon for window: |
||||
1. Via parameter in `Window` function (or in `AppWindow` constructor) |
||||
|
||||
```kotlin |
||||
import androidx.compose.desktop.Window |
||||
import androidx.compose.foundation.Image |
||||
import androidx.compose.foundation.layout.fillMaxSize |
||||
import androidx.compose.ui.graphics.asImageAsset |
||||
import androidx.compose.ui.graphics.ImageAsset |
||||
import androidx.compose.ui.Modifier |
||||
import java.awt.image.BufferedImage |
||||
import java.io.ByteArrayOutputStream |
||||
import java.io.File |
||||
import javax.imageio.ImageIO |
||||
import org.jetbrains.skija.Image |
||||
|
||||
fun main() { |
||||
val image = getWindowIcon() |
||||
Window( |
||||
icon = image |
||||
) { |
||||
val imageAsset = asImageAsset(image) |
||||
Image( |
||||
asset = imageAsset, |
||||
modifier = Modifier.fillMaxSize() |
||||
) |
||||
} |
||||
} |
||||
|
||||
fun getWindowIcon(): BufferedImage { |
||||
var image: BufferedImage? = null |
||||
try { |
||||
image = ImageIO.read(File("sample.png")) |
||||
} catch (e: Exception) { |
||||
// image file does not exist |
||||
} |
||||
|
||||
if (image == null) { |
||||
image = BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB) |
||||
} |
||||
|
||||
return image |
||||
} |
||||
|
||||
fun asImageAsset(image: BufferedImage): ImageAsset { |
||||
val baos = ByteArrayOutputStream() |
||||
ImageIO.write(image, "png", baos) |
||||
|
||||
return Image.makeFromEncoded(baos.toByteArray()).asImageAsset() |
||||
} |
||||
``` |
||||
|
||||
2. Using `setIcon()` method |
||||
|
||||
```kotlin |
||||
import androidx.compose.desktop.AppManager |
||||
import androidx.compose.desktop.Window |
||||
import androidx.compose.foundation.Image |
||||
import androidx.compose.foundation.layout.fillMaxSize |
||||
import androidx.compose.ui.graphics.asImageAsset |
||||
import androidx.compose.ui.graphics.ImageAsset |
||||
import androidx.compose.ui.Modifier |
||||
import java.awt.image.BufferedImage |
||||
import java.io.ByteArrayOutputStream |
||||
import java.io.File |
||||
import javax.imageio.ImageIO |
||||
import org.jetbrains.skija.Image |
||||
|
||||
fun main() { |
||||
val image = getWindowIcon() |
||||
Window { |
||||
val imageAsset = asImageAsset(image) |
||||
Image( |
||||
asset = imageAsset, |
||||
modifier = Modifier.fillMaxSize() |
||||
) |
||||
} |
||||
|
||||
val current = AppManager.focusedWindow |
||||
if (current != null) { |
||||
current.setIcon(image) |
||||
} |
||||
} |
||||
|
||||
fun getWindowIcon(): BufferedImage { |
||||
var image: BufferedImage? = null |
||||
try { |
||||
image = ImageIO.read(File("sample.png")) |
||||
} catch (e: Exception) { |
||||
// image file does not exist |
||||
} |
||||
|
||||
if (image == null) { |
||||
image = BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB) |
||||
} |
||||
|
||||
return image |
||||
} |
||||
|
||||
fun asImageAsset(image: BufferedImage): ImageAsset { |
||||
val baos = ByteArrayOutputStream() |
||||
ImageIO.write(image, "png", baos) |
||||
|
||||
return Image.makeFromEncoded(baos.toByteArray()).asImageAsset() |
||||
} |
||||
``` |
||||
|
||||
![Window icon](window_icon.png) |
||||
|
||||
## Setting the application tray icon |
||||
|
||||
You can create tray icon for your application: |
||||
|
||||
```kotlin |
||||
import androidx.compose.desktop.AppManager |
||||
import androidx.compose.desktop.Window |
||||
import androidx.compose.foundation.Image |
||||
import androidx.compose.foundation.layout.fillMaxSize |
||||
import androidx.compose.runtime.onActive |
||||
import androidx.compose.runtime.onDispose |
||||
import androidx.compose.ui.graphics.asImageAsset |
||||
import androidx.compose.ui.graphics.ImageAsset |
||||
import androidx.compose.ui.Modifier |
||||
import androidx.compose.ui.window.Tray |
||||
import androidx.compose.ui.window.MenuItem |
||||
import java.awt.image.BufferedImage |
||||
import java.io.ByteArrayOutputStream |
||||
import java.io.File |
||||
import javax.imageio.ImageIO |
||||
import org.jetbrains.skija.Image |
||||
|
||||
fun main() { |
||||
val image = getWindowIcon() |
||||
Window { |
||||
onActive { |
||||
val tray = Tray().apply { |
||||
icon(getWindowIcon()) |
||||
menu( |
||||
MenuItem( |
||||
name = "Quit App", |
||||
onClick = { AppManager.exit() } |
||||
) |
||||
) |
||||
} |
||||
onDispose { |
||||
tray.remove() |
||||
} |
||||
} |
||||
|
||||
val imageAsset = asImageAsset(image) |
||||
Image( |
||||
asset = imageAsset, |
||||
modifier = Modifier.fillMaxSize() |
||||
) |
||||
} |
||||
|
||||
val current = AppManager.focusedWindow |
||||
if (current != null) { |
||||
current.setIcon(image) |
||||
} |
||||
} |
||||
|
||||
fun getWindowIcon(): BufferedImage { |
||||
var image: BufferedImage? = null |
||||
try { |
||||
image = ImageIO.read(File("sample.png")) |
||||
} catch (e: Exception) { |
||||
// image file does not exist |
||||
} |
||||
|
||||
if (image == null) { |
||||
image = BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB) |
||||
} |
||||
|
||||
return image |
||||
} |
||||
|
||||
fun asImageAsset(image: BufferedImage): ImageAsset { |
||||
val baos = ByteArrayOutputStream() |
||||
ImageIO.write(image, "png", baos) |
||||
|
||||
return Image.makeFromEncoded(baos.toByteArray()).asImageAsset() |
||||
} |
||||
``` |
||||
|
||||
![Tray icon](tray_icon.png) |
After Width: | Height: | Size: 245 KiB |
After Width: | Height: | Size: 316 KiB |
After Width: | Height: | Size: 318 KiB |
Loading…
Reference in new issue