How to load image from remote url in Kotlin Compose desktop? - android-jetpack-compose

How to load image from remote url in Kotlin Compose desktop?
in Android it use coli as official sample
#Composable
fun NetworkImage(
url: String,
modifier: Modifier = Modifier,
contentScale: ContentScale = ContentScale.Crop,
placeholderColor: Color? = MaterialTheme.colors.compositedOnSurface(0.2f)
) {
CoilImage(
data = url,
modifier = modifier,
contentScale = contentScale,
loading = {
if (placeholderColor != null) {
Spacer(
modifier = Modifier
.fillMaxSize()
.background(placeholderColor)
)
}
}
)
}
bu as desktop application, aar is not supported.

Use javax.imageio.ImageIO to load an image from network:
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import org.jetbrains.skija.Image
import java.io.ByteArrayOutputStream
import java.net.HttpURLConnection
import java.net.URL
import javax.imageio.ImageIO
fun loadNetworkImage(link: String): ImageBitmap {
val url = URL(link)
val connection = url.openConnection() as HttpURLConnection
connection.connect()
val inputStream = connection.inputStream
val bufferedImage = ImageIO.read(inputStream)
val stream = ByteArrayOutputStream()
ImageIO.write(bufferedImage, "png", stream)
val byteArray = stream.toByteArray()
return Image.makeFromEncoded(byteArray).asImageBitmap()
}
And then use it as:
Image(
bitmap = loadNetworkImage("Your image link")
)

A simple way I found was to use ktor to get the image as a byteArray and then use the Kotlin function makeFromEncoded to create a ImageBitmap which can then be used in a Composable Image
suspend fun loadPicture(url: String): ImageBitmap {
val image = ktorHttpClient.use { client ->
client.get<ByteArray>(url)
}
return Image.makeFromEncoded(image).asImageBitmap()
}
Then inside your composable
if (imageBitmap != null) {
Image(bitmap = imageBitmap, "content description")
}

Here's the approach I found.
import androidx.compose.ui.graphics.toComposeImageBitmap
import org.jetbrains.skia.Image
import java.net.URL
fun loadImage(url: String) =
Image.makeFromEncoded(URL(url).readBytes())
.toComposeImageBitmap()

Related

How to render(or convert) a composable to image in compose-desktop

As the title says, hot to render a composable to image.
I want to render a composable to image on my server and send it to client
I have try for these code, bat it does not work.
SwingUtilities.invokeLater {
val composePanel = ComposePanel()
composePanel.setSize(2000,2000)
composePanel.setContent {
MaterialTheme {
Surface(
modifier = Modifier.size(300.dp)
) {
Text("asdasdsad")
}
}
}
val bounds=Rectangle()
composePanel.getBounds(bounds)
val img=BufferedImage(
(bounds.getX() + bounds.getWidth()).toInt(),
(bounds.getY() + bounds.getHeight()).toInt(),
BufferedImage.TYPE_INT_ARGB
)
composePanel.print(img.graphics)
val out=File("test.png")
out.delete()
ImageIO.write(img, "png", out)
}
Now I found a solution.The code below works fine.
But I didn't find a better way to direct convert Image to BufferedImage.
val scene=ImageComposeScene(2000,2000, density = Density(3f), coroutineContext = Dispatchers.Unconfined){
MaterialTheme {
Surface(
modifier = Modifier.size(300.dp)
) {
Text("asdasdsad")
}
}
}
val img=scene.render()
val bitmap=Bitmap.makeFromImage(img)
val bufferedImage=bitmap.toBufferedImage()
val out=File("test.png")
out.delete()
ImageIO.write(bufferedImage, "png", out)

NullPointerException at androidx.compose.ui.platform.RenderNodeLayer.updateDisplayList

I have a Drawer on my MainScreen which navigates me through drawer screens, but for some reason when I try to navigate to two specific screens (this only happens at the first time when I open the app), my app crashes with this error:
FATAL EXCEPTION: main
Process: com.tags.taglife, PID: 21478
java.lang.NullPointerException at androidx.compose.ui.platform.RenderNodeLayer.updateDisplayList(RenderNodeLayer.android.kt:245) at androidx.compose.ui.platform.AndroidComposeView.dispatchDraw(AndroidComposeView.android.kt:760) at android.view.View.draw(View.java:24398) at android.view.View.updateDisplayListIfDirty(View.java:23256) at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4732) at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4704) at android.view.View.updateDisplayListIfDirty(View.java:23203) at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4732) at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4704) at android.view.View.updateDisplayListIfDirty(View.java:23203)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4732)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4704)
at android.view.View.updateDisplayListIfDirty(View.java:23203)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4732)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4704)
at android.view.View.updateDisplayListIfDirty(View.java:23203)
at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:753)
at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:759)
at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:857)
at android.view.ViewRootImpl.draw(ViewRootImpl.java:5501)
at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:5194)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:4356)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2991)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:10665)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1301)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1309)
at android.view.Choreographer.doCallbacks(Choreographer.java:923)
at android.view.Choreographer.doFrame(Choreographer.java:852)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1283)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:226)
at android.os.Looper.loop(Looper.java:313)
at android.app.ActivityThread.main(ActivityThread.java:8741)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1067)
This is the code for one of those screens:
package com.tags.taglife.presentation.MainScreen.sidebar
import android.annotation.SuppressLint
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.foundation.text.appendInlineContent
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.Placeholder
import androidx.compose.ui.text.PlaceholderVerticalAlign
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import com.tags.taglife.presentation.MainScreen.MainViewModel
import com.tags.taglife.presentation.MainScreen.NavScreens
import com.tags.taglife.presentation.ProgressScreen
import com.tags.taglife.presentation.tutorial.ItemModel
import com.tags.taglife.presentation.tutorial.TutorialViewModel
import com.tags.taglife.ui.theme.bottomLine
import com.tags.taglife.ui.theme.white
import taglife.R
#SuppressLint("CoroutineCreationDuringComposition")
#Composable
fun Tutorial(
modifier: Modifier = Modifier,
viewModel: MainViewModel,
tutorialViewModel: TutorialViewModel = hiltViewModel()
) {
viewModel.setCurrentScreen(NavScreens.DrawerScreens.Tutorial)
val isDark = tutorialViewModel.isDarkTheme
val loading = tutorialViewModel.isLoaded
if (!loading.value)
ProgressScreen()
else {
Column(
modifier = modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState())
.padding(20.dp),
) {
//INSTRUCTIONS FOR USE
Title(R.string.instructions_for_use)
tutorialViewModel.instructions.mapIndexed { index, item ->
Item(isDark = isDark.value, item = item, index = index + 1)
}
Spacer(modifier = Modifier.height(15.dp))
//FUNCTIONS
Title(text = R.string.functions_of_homepage_buttons)
tutorialViewModel.functions.mapIndexed { index, item ->
Item(isDark = isDark.value, item = item, index = index + 1)
}
Spacer(modifier = Modifier.height(15.dp))
//QR CODE
Title(text = R.string.qr_code_functions)
tutorialViewModel.qrCodeFunctions.mapIndexed { index, item ->
Item(isDark = isDark.value, item = item, index = index + 1)
}
Spacer(modifier = Modifier.height(15.dp))
//NON NFC SCAN TAG
Title(text = R.string.non_nfc_scan_tag)
tutorialViewModel.nonNFCScanTag.mapIndexed { index, item ->
Item(isDark = isDark.value, item = item, index = index + 1)
}
}
}
}
#Composable
fun Title(text: Int) {
Text(
text = stringResource(id = text),
fontWeight = FontWeight.Bold,
fontSize = 20.sp,
color = MaterialTheme.colors.primary,
)
}
#Composable
fun Item(
isDark : Boolean,
item: List<ItemModel>,
index: Int
) {
val myId = index.toString()
var image: Int = R.drawable.vector
var mainText = buildAnnotatedString {
append("$index. ")
item.forEach {
if (it.text != null)
append(stringResource(id = it.text!!))
if (it.icon != null) {
image = it.icon!!
appendInlineContent(myId, "[icon]")
}
}
}
val inlineContent = mapOf(
Pair(
myId,
InlineTextContent(
Placeholder(
width = 30.sp,
height = 30.sp,
placeholderVerticalAlign = PlaceholderVerticalAlign.TextCenter
)
) {
Image(
painter = painterResource(image),
modifier = Modifier.padding(5.dp),
contentDescription = "",
colorFilter = if (isDark) ColorFilter.tint(white) else null
)
}
)
)
Column(
modifier = Modifier
.fillMaxWidth(), verticalArrangement = Arrangement.Center
) {
Text(
text = mainText,
inlineContent = inlineContent,
textAlign = TextAlign.Start,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 12.dp)
)
Spacer(
modifier = Modifier
.height(1.dp)
.fillMaxWidth()
.background(bottomLine)
)
}
}
I tried simply to navigate to those screens via drawer.
I figured it out.
I needed to update my whole project to a new jetpack compose version, and compileSdk to 33.

I have a composable not setting button text as expected; wondering why. Have a reproducible example

this started as a new compose project
with the following code the intent is to change the text to the picked time. The code is commented where the behavior occurs
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApplicationTestTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
TimeCardButton(id = 1, symbol ="In", enabled=true,modifier = Modifier) { entry ->
Log.d("click", "$entry result")
}
}
}
}
}
}
data class TimeCardEntry(val id: Int = -1, var entry: String = "")
#Composable
fun TimeCardButton(
id: Int,
symbol: String,
enabled: Boolean = false,
modifier: Modifier,
onValueChange: (TimeCardEntry) -> Unit = {},
) {
// Value for storing time as a string
val timeState = remember {
mutableStateOf(TimeCardEntry(id, symbol))
}
val validState = remember {
timeState.value.entry.trim().isNotEmpty()
}
val mTime = remember { mutableStateOf(symbol) }
if (enabled) {
// Fetching local context
val mContext = LocalContext.current
// Declaring and initializing a calendar
val mCalendar = Calendar.getInstance()
val mHour = mCalendar[Calendar.HOUR_OF_DAY]
val mMinute = mCalendar[Calendar.MINUTE]
// Creating a TimePicker dialog
val mTimePickerDialog = TimePickerDialog(
mContext,
{ _, mHour: Int, mMinute: Int ->
timeState.value.entry = "$mHour:$mMinute"
mTime.value = "$mHour:$mMinute"
onValueChange(timeState.value)
}, mHour, mMinute, false
)
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.clip(CircleShape)
.then(modifier)
) {
TextButton(onClick = { mTimePickerDialog.show() }.also {
Log.d("click", "id $id clicked!") }) {
Column() {
// if I use just this it works [in changes to the time picked]
//Text(text = mTime.value)
// if i use both of these BOTH are set when the date picker is invoked
// if I just use the second one alone, the text never changes
Text(text = timeState.value.entry)
}
}
}
} else {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.clip(CircleShape)
.then(modifier)
) {
Text(text = symbol, color =
MaterialTheme.colors.onBackground)
}
}
}
#Preview(showBackground = true)
#Composable
fun DefaultPreview() {
MyApplicationTestTheme {
}
}
First of all how to fix it:
Your problem basically is this. The easiest way to fix it would be to reassign the whole value of TimeState, not just entry by calling
timeState.value = timeState.value.copy(entry = "$mHour:$mMinute")
The reason it doesn't work with only the second one is that the change of a property doesn't trigger recomposition, even if the variable containing it is a mutableState. To fix (as outlined in the answers to the question linked above) this you either have to reassign the whole variable or make the parameter you want to observe observable (for example changing the String to State<String>)
PS: if you use by with mutableStateOf (i.e. val timeState = remember { mutableStateOf(TimeCardEntry(id, symbol)) }) you don't have to use .value every time. I find that a lot cleaner and more readable

How to convert SvelteKit fetch response to a buffer?

I have a simple node script to fetch tif image, use sharp to convert the image to jpeg and generate data:image/jpeg;base64 src for browser. The reason is to enable tiff image in browsers that do not support tif/tiff images.
In node this works great.
import sharp from "sharp";
import fetch from "node-fetch";
let link =
"https://people.math.sc.edu/Burkardt/data/tif/at3_1m4_01.tif";
// fetch tif image and save it as a buffer
async function fetchTifBuffer(link) {
const tifImg = await fetch(link);
const buffer = await tifImg.buffer();
return buffer;
}
// convert to png image save to buffer and save as base64
async function tifToPngToBase64(link) {
let inputImgBuffer = await fetchTifBuffer(link);
const buff = await sharp(inputImgBuffer).toFormat("jpeg").toBuffer();
let base64data = buff.toString("base64");
let imgsrc = "data:image/jpeg;base64," + base64data.toString("base64");
console.log(imgsrc);
// use in browser <img src="data:image/jpeg;base64,/9j/2wBDAAYEBQYFBAY ...==" alt="image" />
}
tifToPngToBase64(link);
I would like to implement this in SvelteKit. But I have no idea how to get buffer from SvelteKit fetch response. Any ideas?
Ok, so I figured it out by myself. SvelteKit fetch response has an arrayBuffer, so all that is needed is to convert the arraybuffer to the buffer.
This is the relevant SvelteKit endpoint:
// img.js
import sharp from "sharp";
export const get = async () => {
const res = await fetch(
"https://people.math.sc.edu/Burkardt/data/tif/at3_1m4_01.tif"
);
const abuffer = await res.arrayBuffer();
const buffer = Buffer.from(new Uint8Array(abuffer));
const buff = await sharp(buffer).toFormat("jpeg").toBuffer();
let base64data = buff.toString("base64");
let src = `data:image/jpeg;base64,${base64data.toString("base64")}`;
let img = `<img style='display:block; width:100px;height:100px;' id='base64image'
src='${src}' />`;
return {
body: {
img,
},
};
};
and this is the SvelteKit component which uses the endpont:
// img.svelte
<script>
export let img;
</script>
{#html img}

How can I judge whether the code will run inside of the Composition or outside of the Composition in Android Studio Compose?

The following content is from the article.
Using LaunchedEffect in this case wasn't possible because we needed to trigger the call to create a coroutine in a regular callback that was outside of the Composition.
I was told that I should use LaunchedEffect when the code run inside of the Composition, and I should use rememberCoroutineScope when the code run outside of the Composition.
The Code A and Code B are samples from the article above.
But I don't understand why scaffoldState.drawerState.open() will run outside of the Compositionn in Code A? You know that fun CraneHome() is #Composable.
Code A
#Composable
fun CraneHome(
onExploreItemClicked: OnExploreItemClicked,
modifier: Modifier = Modifier,
) {
val scaffoldState = rememberScaffoldState()
Scaffold(
scaffoldState = scaffoldState,
modifier = Modifier.statusBarsPadding(),
drawerContent = {
CraneDrawer()
}
) {
val scope = rememberCoroutineScope()
CraneHomeContent(
modifier = modifier,
onExploreItemClicked = onExploreItemClicked,
openDrawer = {
scope.launch {
scaffoldState.drawerState.open()
}
}
)
}
}
Code B
#Composable
fun LandingScreen(modifier: Modifier = Modifier, onTimeout: () -> Unit) {
Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
val currentOnTimeout by rememberUpdatedState(onTimeout)
LaunchedEffect(true) {
delay(SplashWaitTime)
currentOnTimeout()
}
Image(painterResource(id = R.drawable.ic_crane_drawer), contentDescription = null)
}
}

Resources