From a2483c85d73201d4534f71a530a43353052d35d8 Mon Sep 17 00:00:00 2001 From: Timo Bryant Date: Sat, 30 Dec 2023 22:53:11 +0100 Subject: [PATCH] starting with canvas --- Writerside/topics/docthor.md | 3 + apps/documentViewer/build.gradle.kts | 1 + .../de/itkl/documentViewer/DocumentViewer.kt | 166 ++++++++++++++---- ...cthor.kotlin-common-conventions.gradle.kts | 2 + .../de/itkl/httpClient/clients/MsOcr.kt | 2 +- 5 files changed, 143 insertions(+), 31 deletions(-) diff --git a/Writerside/topics/docthor.md b/Writerside/topics/docthor.md index a053f24..23957b0 100644 --- a/Writerside/topics/docthor.md +++ b/Writerside/topics/docthor.md @@ -17,6 +17,9 @@ Asset can be found under memento:/mnt/wd/export/data Building modern, elegant and fast desktop Compose applications + +Zooming an Image + ## Modules - Libraries diff --git a/apps/documentViewer/build.gradle.kts b/apps/documentViewer/build.gradle.kts index 87c9a51..25e7f04 100644 --- a/apps/documentViewer/build.gradle.kts +++ b/apps/documentViewer/build.gradle.kts @@ -12,4 +12,5 @@ dependencies { implementation("org.pushing-pixels:aurora-window:1.3.0") implementation(compose.desktop.currentOs) implementation("io.github.panpf.zoomimage:zoomimage-compose:1.0.0-beta11") + implementation("io.github.panpf.zoomimage:zoomimage-compose-desktop:1.0.0-beta11") } \ No newline at end of file diff --git a/apps/documentViewer/src/main/kotlin/de/itkl/documentViewer/DocumentViewer.kt b/apps/documentViewer/src/main/kotlin/de/itkl/documentViewer/DocumentViewer.kt index b3a6e24..523a64a 100644 --- a/apps/documentViewer/src/main/kotlin/de/itkl/documentViewer/DocumentViewer.kt +++ b/apps/documentViewer/src/main/kotlin/de/itkl/documentViewer/DocumentViewer.kt @@ -1,20 +1,34 @@ package de.itkl.documentViewer -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.width +import androidx.compose.desktop.ui.tooling.preview.Preview +import androidx.compose.foundation.* +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsHoveredAsState +import androidx.compose.foundation.layout.* +import androidx.compose.material.Text import androidx.compose.runtime.* -import androidx.compose.ui.text.ExperimentalTextApi import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.scale +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.drawscope.DrawStyle +import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.onPointerEvent +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.loadImageBitmap -import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.drawText import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import androidx.compose.ui.window.WindowPlacement @@ -23,12 +37,10 @@ import androidx.compose.ui.window.rememberWindowState import com.github.panpf.zoomimage.ZoomImage import com.github.panpf.zoomimage.compose.ZoomState import com.github.panpf.zoomimage.compose.rememberZoomState -import com.github.panpf.zoomimage.compose.subsampling.fromResource -import com.github.panpf.zoomimage.subsampling.ImageSource +import com.github.panpf.zoomimage.compose.zoom.ScrollBarSpec +import com.github.panpf.zoomimage.compose.zoom.ZoomableState import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import org.pushingpixels.aurora.component.model.Command -import org.pushingpixels.aurora.component.projection.CommandButtonProjection import org.pushingpixels.aurora.theming.auroraBackground import org.pushingpixels.aurora.theming.marinerSkin import org.pushingpixels.aurora.window.AuroraWindow @@ -36,17 +48,17 @@ import org.pushingpixels.aurora.window.AuroraWindowTitlePaneConfigurations import org.pushingpixels.aurora.window.auroraApplication import java.io.File import java.io.IOException -import java.nio.file.Paths +import io.github.oshai.kotlinlogging.KotlinLogging +import com.github.panpf.zoomimage.util.Logger as ZoomLogger -class DocumentViewer { -} +private val Log = KotlinLogging.logger { } fun main() = auroraApplication { val state = rememberWindowState( placement = WindowPlacement.Floating, position = WindowPosition.Aligned(Alignment.Center), - size = DpSize(220.dp, 150.dp) + size = DpSize(1000. dp, 800.dp) ) AuroraWindow( @@ -56,31 +68,37 @@ fun main() = auroraApplication { windowTitlePaneConfiguration = AuroraWindowTitlePaneConfigurations.AuroraPlain(), onCloseRequest = ::exitApplication ) { - var text by remember { mutableStateOf("Hello, World!") } + viewImage() + } +} - Row( - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxSize().auroraBackground() +@Composable +@Preview +fun viewImage() { + Column ( + modifier = Modifier.fillMaxSize().auroraBackground() + ) { + val state = rememberZoomState(logger = ZoomLogger("zoom", level = ZoomLogger.INFO)) + Text("${state.zoomable.transform.scale} ${state.zoomable.transform.offset}") + Box( + modifier = Modifier.fillMaxSize() ) { - AsyncImage( + ZoomedImage( + state = state, load = { loadImageBitmap(File("assets/xs-reg/00001.jpg")) }, painterFor = { remember { BitmapPainter(it) } }, contentDescription = "Sample", modifier = Modifier.fillMaxSize() - ) -// CommandButtonProjection( -// contentModel = Command( -// text = text, -// action = { text = "Hello, Desktop!" } -// ) -// ).project() + canvas(state.zoomable) +// shapes(state.zoomable) } } } + @Composable -fun AsyncImage( +fun ZoomedImage( + state: ZoomState, load: suspend () -> T, painterFor: @Composable (T) -> Painter, contentDescription: String, @@ -101,13 +119,101 @@ fun AsyncImage( } if (image != null) { + val scrollBar = remember { + ScrollBarSpec( + color = Color.Red, + size = 6.dp, + margin = 12.dp, + ) + } ZoomImage( painter = painterFor(image!!), contentDescription = contentDescription, contentScale = contentScale, - modifier = modifier + modifier = modifier, + scrollBar = scrollBar, + state = state ) } } fun loadImageBitmap(file: File): ImageBitmap = file.inputStream().buffered().use(::loadImageBitmap) + +data class PointConverter( + val docWidth: Int, + val docHeight: Int, + val canvasWidth: Float, + val canvasHeight: Float +) { + fun convertX(x: Int): Float { + val xf = x.toFloat() + val relXf = docWidth / xf + val scaledXf = canvasWidth * relXf +// println("X: $scaledXf") + return scaledXf + } + fun convertY(y: Int): Float { + val yf = y.toFloat() + val relYf = docHeight / yf + val scaledYf = canvasHeight * relYf +// println("Y: $scaledYf") + return scaledYf + } +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun shapes(zoomableState: ZoomableState) { + Box(modifier = Modifier.fillMaxSize()) { + Box( + modifier = Modifier + .offset(100.dp) + .size(100.dp) + .clip(RectangleShape) + .background(Color.Red) + .onClick(true) { println("clicked") } + ) + } +} +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun canvas(zoomableState: ZoomableState) { + Canvas(modifier = Modifier + .fillMaxSize() + .onPointerEvent(PointerEventType.Move) { + val position = it.changes.first().position + println(position) + } + ) + { + val converter = PointConverter( + docWidth = 2481, + docHeight = 3507, + canvasWidth = this.size.width, + canvasHeight = this.size.height + ) +// val width = 2481 +// val height = 3507 + +// val x = +// "width": 2481, +// "height": 3507, +// "boundingBox": [ +// 288, +// 697, +// 793, +// 700, +// 793, +// 744, +// 288, +// 741 +// ], + drawRect( + Color.Blue, +// topLeft = Offset(converter.convertX(288), converter.convertY(697)), + topLeft = zoomableState.transform.offset + Offset(800f,800f), + size = Size(30f, 30f), + style = Stroke(width = 5f) + ) + } +} diff --git a/buildSrc/src/main/kotlin/docthor.kotlin-common-conventions.gradle.kts b/buildSrc/src/main/kotlin/docthor.kotlin-common-conventions.gradle.kts index ab74060..a997a22 100644 --- a/buildSrc/src/main/kotlin/docthor.kotlin-common-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/docthor.kotlin-common-conventions.gradle.kts @@ -1,3 +1,4 @@ +import gradle.kotlin.dsl.accessors._d9dcfd1a467b0b6fe90c5571a57aa558.api import org.gradle.api.plugins.jvm.JvmTestSuite import org.jetbrains.kotlin.gradle.dsl.JvmTarget @@ -17,6 +18,7 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2") + api("io.github.oshai:kotlin-logging-jvm:5.1.0") testImplementation("io.insert-koin:koin-test:$koin_version") } diff --git a/libraries/httpClient/src/main/kotlin/de/itkl/httpClient/clients/MsOcr.kt b/libraries/httpClient/src/main/kotlin/de/itkl/httpClient/clients/MsOcr.kt index ea56045..0ea6c06 100644 --- a/libraries/httpClient/src/main/kotlin/de/itkl/httpClient/clients/MsOcr.kt +++ b/libraries/httpClient/src/main/kotlin/de/itkl/httpClient/clients/MsOcr.kt @@ -24,7 +24,7 @@ class MsOcr: KoinComponent { contentType(resource.contentType) setBody(resource.read()) } - println("got response: ${response.status} in ${response.responseTime}") + println(response.bodyAsText()) return response.body() } } \ No newline at end of file