当前位置: 首页 > wzjs >正文

怎么建医疗网站网页制作网页设计

怎么建医疗网站,网页制作网页设计,wordpress 主题 开源,鲜花网站建设图片截图功能 Compose MultiplatformKotlin Multiplatfrom下实现桌面端的截图功能,起码搞了两星期,最后终于做出来了,操作都很流畅,截取的文件大小也正常,可参考支持讨论! 功能效果 代码实现 //在jvmMain下创…

截图功能

  Compose Multiplatform+Kotlin Multiplatfrom下实现桌面端的截图功能,起码搞了两星期,最后终于做出来了,操作都很流畅,截取的文件大小也正常,可参考支持讨论!

功能效果

windows截图
mac截图
截图缓存目录

代码实现

//在jvmMain下创建TestCapture11.kt,完整的截图核心功能代码package com.hwj.ai.captureimport androidx.compose.foundation.Canvas
import androidx.compose.foundation.focusable
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.awt.ComposeWindow
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.isSecondaryPressed
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.WindowPosition
import androidx.compose.ui.window.rememberWindowState
import com.hwj.ai.global.printD
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.awt.GraphicsEnvironment
import java.awt.Rectangle
import java.awt.Robot
import java.awt.Toolkit
import java.awt.image.BufferedImage
import java.io.File
import javax.imageio.ImageIO//UI 拖拽得到的坐标 * scaleFactor ≠ 实际屏幕坐标
//macOS 多屏/高DPI 下 Robot 截图区域
//效果suc,windows/macOS 主屏幕
class ScreenshotState11 {var startOffset by mutableStateOf(Offset.Zero)var endOffset by mutableStateOf(Offset.Zero)val selectionRect: Rectget() = Rect(startOffset, endOffset)var isSelecting by mutableStateOf(false)
}val LocalMainWindow = staticCompositionLocalOf<ComposeWindow> { error("No Window provided") }@Composable
fun ScreenshotOverlay11(mainWindow: ComposeWindow,onCapture: (BufferedImage) -> Unit,onCancel: () -> Unit
) {var showActBtn by remember { mutableStateOf(false) }var capturedRect by remember { mutableStateOf<Rect?>(null) }val state = remember { ScreenshotState11() }val screenSize = Toolkit.getDefaultToolkit().screenSizeval windowState = rememberWindowState(position = WindowPosition(0.dp, 0.dp),size = DpSize(screenSize.width.dp, screenSize.height.dp))val subScope = rememberCoroutineScope()val focusReq = remember { FocusRequester() }LaunchedEffect(Unit) {mainWindow.isVisible = false}Window(onCloseRequest = {onCancel()mainWindow.isVisible = true},state = windowState,transparent = true,undecorated = true,alwaysOnTop = true,focusable = true,resizable = false) {LaunchedEffect(Unit) {window.requestFocus() //触发快捷键focusReq.requestFocus()}Box(modifier = Modifier.fillMaxSize().focusRequester(focusReq).focusable().pointerInput(Unit) { //识别鼠标右键取消awaitPointerEventScope {while (true) {val event = awaitPointerEvent()val pressed = event.buttons.isSecondaryPressedif (event.type == PointerEventType.Press && pressed) {onCancel()mainWindow.isVisible = true}}}}.onPreviewKeyEvent { keyEvent ->
//                printD("keyEvent>${keyEvent.key} ${Key.Escape}")if (keyEvent.key == Key.Escape) { //快捷键取消onCancel()mainWindow.isVisible = truetrue} else {false}}.pointerInput(Unit) {detectDragGestures(onDragStart = { offset ->state.isSelecting = truestate.startOffset = offsetstate.endOffset = offset},onDrag = { change, _ ->state.endOffset = change.position},onDragEnd = {capturedRect = state.selectionRect.normalize()//如果只是点了两下,宽高都很少,不足以被认定为截图!showActBtn = true})}) {Canvas(modifier = Modifier.fillMaxSize()) {// 背景遮罩drawRect(Color.Black.copy(alpha = 0.3f))// 选区范围和尺寸if (state.isSelecting) {val rect = state.selectionRect.normalize()// 半透明填充drawRect(color = Color.White.copy(alpha = 0.05f),topLeft = rect.topLeft,size = rect.size)// 白色描边drawRect(color = Color.White,topLeft = rect.topLeft,size = rect.size,style = Stroke(width = 2.dp.toPx()))}}//在截图框下放按钮if (showActBtn && capturedRect != null) {val isFullCapture =capturedRect!!.width > screenSize.width * 0.8f && capturedRect!!.height > screenSize.height * 0.8fval myModifier: Modifierif (isFullCapture) {myModifier = Modifier.align(Alignment.TopEnd).padding(23.dp)} else {val offx = with(LocalDensity.current) { capturedRect!!.right.toDp() - 180.dp }val offy = with(LocalDensity.current) { capturedRect!!.bottom.toDp() + 0.dp }myModifier = Modifier.offset(x = offx, y = offy)}Box(modifier = myModifier) {Row(modifier = Modifier.align(Alignment.BottomCenter).padding(13.dp)) {Button(onClick = {showActBtn = falsemainWindow.isVisible = truestate.isSelecting = false //有必要吗capturedRect = null //点击取消没有退出onCancel() //关闭}) {Text("取消", color = Color.White)}Spacer(modifier = Modifier.width(10.dp))Button(onClick = {if (null != capturedRect) {subScope.launch {captureSelectedArea(capturedRect!!) { pic ->//                                        val thumbnail =
//                                            BufferedImage(
//                                                200,
//                                                200,
//                                                BufferedImage.TYPE_INT_ARGB
//                                            ).apply {
//                                                createGraphics().drawImage(
//                                                    pic.getScaledInstance(
//                                                        200,
//                                                        200,
//                                                        java.awt.Image.SCALE_SMOOTH
//                                                    ),
//                                                    0, 0, null
//                                                )
//                                            }
//                                        onCapture(thumbnail) //传缩略图onCapture(pic)}withContext(Dispatchers.Main) {showActBtn = falsemainWindow.isVisible = trueonCancel()}}} else {showActBtn = falsemainWindow.isVisible = trueonCancel()}}) {Text("确定", color = Color.White)}}}}}}
}private suspend fun captureSelectedArea(rect: Rect, onSuccess: (BufferedImage) -> Unit) {val normalizedRect = rect.normalize()val screenDevices = GraphicsEnvironment.getLocalGraphicsEnvironment().screenDevicesvar targetDevice: java.awt.GraphicsDevice? = null//    // 找到选区落在哪块屏幕上for (device in screenDevices) {val bounds = device.defaultConfiguration.boundsif (bounds.contains(normalizedRect.left.toInt(), normalizedRect.top.toInt())) {targetDevice = devicebreak}}//多屏不让用
//    if (screenDevices.size>1){
//        NotificationsManager().showNotification("不支持多屏截图!","不支持多屏截图")
//        return
//    }//    targetDevice=screenDevices[0]if (targetDevice == null) {targetDevice =java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment().defaultScreenDevice}val config = targetDevice!!.defaultConfigurationval screenBounds = config.bounds // 屏幕偏移(多屏布局下重要)val transform = config.defaultTransformval scaleX = transform.scaleXval scaleY = transform.scaleY
//    printD("屏幕 bounds: $screenBounds, scaleX: $scaleX, scaleY: $scaleY")// 关键:Compose 逻辑坐标 → 物理像素坐标 ,超级大坑,用chatgpt写代码一直反馈是乘scaleX,实际是除以,不然容易黑屏val captureX = (normalizedRect.left / scaleX).toInt()val captureY = (normalizedRect.top / scaleY).toInt()val captureW = (normalizedRect.width / scaleX).toInt()val captureH = (normalizedRect.height / scaleY).toInt()//    printD("最终截图区域 (物理像素): x=$captureX, y=$captureY, w=$captureW, h=$captureH")if (captureW <= 0 || captureH <= 0) returntry {// 隐藏截图窗口,防止蒙层被截进去val awtWindow = java.awt.KeyboardFocusManager.getCurrentKeyboardFocusManager().activeWindowawtWindow?.isVisible = falseThread.sleep(100) // 等隐藏生效val robot = Robot(targetDevice)val screenRect = Rectangle(captureX, captureY, captureW, captureH)val image = robot.createScreenCapture(screenRect)onSuccess(image)} catch (e: Exception) {e.printStackTrace()}
}fun saveToFile11(image: BufferedImage): String? {//     val desktopPath = System.getProperty("user.home") + File.separator + "Desktop"
//     val file = File(desktopPath, "screenshot_${System.currentTimeMillis()}.png")val cacheDir = getPlatformCacheImgDir()if (!cacheDir.exists()) cacheDir.mkdirs()val file = File(cacheDir, "screenshot_${System.currentTimeMillis()}.png")ImageIO.write(image, "PNG", file)printD("截图已保存到:${file.absolutePath}")ImageIO.write(image, "PNG", file).also {if (it) return file.absolutePath}return null
}//截图已保存到缓存目录:/Users/你的用户名/Library/Caches/com.hwj.ai.capture/screenshot_1710918988888.png
//截图已保存到缓存目录:C:\Users\你的用户名\AppData\Local\com.hwj.ai.capture\cache\screenshot_1710918988888.png
//截图已保存到缓存目录:/home/你的用户名/.cache/com.hwj.ai.capture/screenshot_1710918988888.png
private fun getPlatformCacheImgDir(): File {val osName = System.getProperty("os.name").lowercase()return if (osName.contains("mac")) {File(System.getProperty("user.home"), "Library/Caches/com.hwj.ai.capture")} else if (osName.contains("win")) {File(System.getenv("LOCALAPPDATA"), "com.hwj.ai.capture/cache")} else {File(System.getProperty("user.home"), ".cache/com.hwj.ai.capture")}
}/*** 扩展方法:统一 start/end,避免负数尺寸*/
private fun Rect.normalize(): Rect {val left = minOf(this.left, this.right)val top = minOf(this.top, this.bottom)val right = maxOf(this.left, this.right)val bottom = maxOf(this.top, this.bottom)return Rect(left, top, right, bottom)
}
//commonMain 下声明接口
@Composable
expect fun ScreenShotPlatform(onSave: (String?) -> Unit)//在jvmMain实现接口内容,其他Android直接空就行了
@Composable
actual fun ScreenShotPlatform(onSave: (String?) -> Unit) {val mainWindow = LocalMainWindow.currentval chatViewModel = koinViewModel(ChatViewModel::class)val isShotState = chatViewModel.isShotState.collectAsState().valueif (isShotState && onlyDesktop()) {ScreenshotOverlay11(mainWindow = mainWindow, onCapture = { pic ->val file = saveToFile11(pic)onSave(file)}, onCancel = {chatViewModel.shotScreen(false)})}
}//调用方式
//在ChatViewModel.kt文件声明截图操作状态,方便全局拉起功能
//是否触发截图private val _isShotObs = MutableStateFlow(false)val isShotState = _isShotObs.asStateFlow()//在composable函数使用val isShotState = chatViewModel.isShotState.collectAsState().value
if (isShotState) {ScreenShotPlatform(onSave = { filePath ->filePath?.let {subScope.launch {conversationViewModel.addCameraImage(PlatformFile(filePath))}}})return}

技术讨论

1.整个思路概述,截图就是新建一个window,在新window进行手势操作,画布绘制,完成截图再把放在单例viewModel的状态变量重置,关闭新window显示主window,不是多window一 开始做手势操作都在应用内,没法在应用外截图。

2.我的截图需求分析是基于java原生api实现的,目前只考虑单显示器,即Windows,Macbook的Retina高分屏,触发截图功能时应用隐藏,主屏幕一层带黑色透明的背景,可多次使用鼠标拖拽绘制选择框,选择框由白色的边框和带白色透明的背景组成,拖拽结束矩形框下方出现操作菜单取消或保存,如果是全屏截图那么菜单按钮则内嵌到选择框的右上角。截图完成可压缩后再保存,但是我看截图文件都不大就注释了,图片路径getPlatformCacheImgDir()注释说明 了。点击保存应用显示,截图功能参数重置,缓存图片并回调图片路径。

3.看预览图windows/mac其实都是共用一套代码,但是菜单按钮显示位置不同时我代码还没同步,修改了下边距,逻辑没问题,然后还有优化点 是某些状态值不是很准确,导致菜单按钮多次取消,后面再修复,大家也可提意见修。

4.实现了快捷键的识别,对Esc可取消截图,鼠标右键也可取消。

5.多屏扩展问题,因为我没有多个显示器,不方便处理这个功能,要注意一个很恶心的问题,mac电脑是高分屏Retina,它的逻辑像素和物理像素有个2倍关系,但是windows是1倍,单屏其实不考虑屏幕偏移量,我在chatgpt示例代码都是把逻辑坐标乘缩放因子,导致我Mac截图一直错位,截黑屏,当时所有AI模型写的都是乘,后来是自己全部截图和测试缩放因子的实际图片发现的问题。

6.macOs系统截图触发会弹权限请求,用户要在系统设置-》隐私与安全性-》屏幕与系统音频录制-》添加应用即可,不然截图就是空白也不报错,调试时发现启用了权限但是线刷程序一样没权限,所以有时我是打包再测,有时又没问题。

7.截图api调用前要等待下线程,不然会把白色的蒙层也截取进去,还有就是注意线程的切换,不然容易造成卡顿 。

总结

  此次是实现java虚拟机的截图功能,很多是思路选择问题,这网上查了没人用compose multiplatform实现截图桌面端,可参考下此文,最近实现豆包的划词工具,也是在Compose Multiplatform下实现,但是目前只实现了剪切板获取用户鼠标选中文字,但是没法恢复用户之前的复制文件,后面解决了再出新文章。也实现了微软的自动化Automataion,不太行,也实现了系统钩子但是只能做到win32的notepad,其他应用不行,有大牛有思路可提意见。

第四弹指达


文章转载自:

http://3NzRtOS9.Lbpqk.cn
http://86KmSAat.Lbpqk.cn
http://yLXpvNi5.Lbpqk.cn
http://NjGWnIQ3.Lbpqk.cn
http://04QyJdXu.Lbpqk.cn
http://ijBvlHtq.Lbpqk.cn
http://QSg597Dt.Lbpqk.cn
http://Msz1B1wt.Lbpqk.cn
http://viIBh7Kk.Lbpqk.cn
http://5HdmUmdY.Lbpqk.cn
http://6vmguhMO.Lbpqk.cn
http://82udNx2I.Lbpqk.cn
http://JETXsFRX.Lbpqk.cn
http://M6aLmpRI.Lbpqk.cn
http://qMkONNF3.Lbpqk.cn
http://ohiPTVge.Lbpqk.cn
http://NPZEJ1u8.Lbpqk.cn
http://mHVd2weV.Lbpqk.cn
http://AEvdWwKx.Lbpqk.cn
http://uYmg52ka.Lbpqk.cn
http://RSPCh5tz.Lbpqk.cn
http://s7STOtWH.Lbpqk.cn
http://UZfbAV6z.Lbpqk.cn
http://d7VbY5vh.Lbpqk.cn
http://Jm77MIZW.Lbpqk.cn
http://Qoybv7qZ.Lbpqk.cn
http://VtNiUG1U.Lbpqk.cn
http://MW6in9EM.Lbpqk.cn
http://0aGbuX0J.Lbpqk.cn
http://kiqaxDkW.Lbpqk.cn
http://www.dtcms.com/wzjs/627641.html

相关文章:

  • 怎么把网站做10万ip企业建网站公司多少钱
  • 域名对行业网站的作用wordpress作者英文版
  • 上百度推广的网站要多少钱六安新闻 最新消息
  • 网站关键字优化教程全国文明城市创建标语
  • 本地wordpress建站建行购物网站
  • 网站建设方案规划书iis 与 wordpress
  • 网站重构案例网页设计文字教程
  • 国办网站建设规范看剧资源网站怎么做的
  • 商城网站模板html网络公司资质包括哪些
  • 搭建好网站如何使用宁波seo公司排名
  • 网站地图在线生成器四川建设学习网官网
  • 网站安全建设总结报告环保工程网站建设价格
  • 门户网站建设管理工作方案廊坊建站
  • 发软文的网站吉林省建设工程造价信息网
  • 网站301重定向代码小程序app系统开发
  • 新乡微信网站建设文字类wordpress主题
  • 织梦cms怎样做网站logo库官网
  • 惠州网站建设企业百度seo排名优化技巧分享
  • 弱电网站源码wordpress读取相册
  • 网站建设关键词排名优化好看的博客页面
  • 网站设计公司上海帝国cms网站搬家
  • 公司网站建设需推广学校网站建设培训方案
  • 上海 响应式网站公司泊头市有做网站的吗
  • 电影采集网站流量免费企业邮箱号有哪些
  • 江西网站开发公司电话wordpress 做思维导图
  • 长春市规划建设局网站查询如何做能切换语言的网站
  • 济宁培训网站建设淘宝官网首页入口手机
  • 上海国际建设总承包公司网站中国站长网站
  • 怀宁做网站培训ui设计公司
  • 公司做网站需要哪些手续文本网站开发英文文献