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

Compose Multiplatform+Kotlin Multiplatfrom 第五弹跨平台 截图

截图功能

  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,其他应用不行,有大牛有思路可提意见。

第四弹指达

相关文章:

Compose Multiplatform+Kotlin Multiplatfrom 第五弹跨平台 截图

截图功能 Compose MultiplatformKotlin Multiplatfrom下实现桌面端的截图功能&#xff0c;起码搞了两星期&#xff0c;最后终于做出来了&#xff0c;操作都很流畅&#xff0c;截取的文件大小也正常&#xff0c;可参考支持讨论&#xff01; 功能效果 代码实现 //在jvmMain下创…...

Elasticearch数据流向

Elasticearch数据流向 数据流向图 --- config: layout: elk look: classic theme: mc --- flowchart LR subgraph s1["图例"] direction TB W["写入流程"] R["读取流程"] end A["Logstash Pipeline"] -- 写入请求 --> B["Elas…...

在docker里装rocketmq-console

首先要到github下载&#xff08;这个一般是需要你有梯子&#xff09; GitHub - apache/rocketmq-externals at release-rocketmq-console-1.0.0 如果没有梯子&#xff0c;用下面这个百度网盘链接下 http://链接: https://pan.baidu.com/s/1x8WQVmaOBjTjss-3g01UPQ 提取码: fu…...

使用Python写入JSON、XML和YAML数据到Excel文件

在当今数据驱动的技术生态中&#xff0c;JSON、XML和YAML作为主流结构化数据格式&#xff0c;因其层次化表达能力和跨平台兼容性&#xff0c;已成为系统间数据交换的通用载体。然而&#xff0c;当需要将这类半结构化数据转化为具备直观可视化、动态计算和协作共享特性的载体时&…...

从零开始构建智能聊天机器人:Rasa与ChatGPT API实战教程

引言&#xff1a;AI对话系统的时代机遇 在数字化转型浪潮中&#xff0c;聊天机器人已成为连接用户与服务的关键纽带。无论是客服系统中的724小时即时响应&#xff0c;还是智能家居中的语音交互&#xff0c;聊天机器人正在重塑人机交互方式。本文将通过详细教程&#xff0c;手把…...

编码常见的 3类 23种设计模式——学习笔记

一、创建型(用于方便创建实例) 1. 单例模式 优点&#xff1a; 确保系统中只有一个实例存在&#xff0c;避免多个实例导致的资源冲突或数据不一致问题。例如&#xff0c;数据库连接池、线程池等全局资源管理器适合用单例实现。 减少频繁创建和销毁对象的开销&#xff0c;尤其适…...

# 实时人脸性别与年龄识别:基于OpenCV与深度学习模型的实现

实时人脸性别与年龄识别&#xff1a;基于OpenCV与深度学习模型的实现 在当今数字化时代&#xff0c;计算机视觉技术正以前所未有的速度改变着我们的生活与工作方式。其中&#xff0c;人脸检测与分析作为计算机视觉领域的重要分支&#xff0c;已广泛应用于安防监控、智能交互、…...

x-cmd install | Slumber - 告别繁琐,拥抱高效的终端 HTTP 客户端

目录 核心优势&#xff0c;一览无遗安装应用场景&#xff0c;无限可能示例告别 GUI&#xff0c;拥抱终端 还在为调试 API 接口&#xff0c;发送 HTTP 请求而苦恼吗&#xff1f;还在各种 GUI 工具之间切换&#xff0c;只为了发送一个简单的请求吗&#xff1f;现在&#xff0c;有…...

apijson 快速上手

apijson是强大的工具&#xff0c;简化了CRUD的操作&#xff0c;只要有数据库表&#xff0c;就能自动生成RESTFUL接口。但初次上手也是摸索了很长时间&#xff0c;尤其是部署与使用上&#xff0c;这里尝试以初学者角度来说下&#xff1a; 一、好处 1、对于简单的应用&#xff…...

3D激光轮廓仪知识整理

文章目录 1.原理和应用场景1.1 相机原理1.1.1 测量原理1.1.2 相机激光器1.1.3 沙姆镜头1.1.4 相机标定1.1.5 中心线提取 1.2 应用场景1.2.1 测量相关应用1.2.2 缺陷检测相关应用 2.相机参数介绍及选型介绍2.1 成像原理2.2 原始图成像2.3 生成轮廓图2.4 相机规格参数2.4.1 单轮廓…...

Stable Diffusion+Pyqt5: 实现图像生成与管理界面(带保存 + 历史记录 + 删除功能)——我的实验记录(结尾附系统效果图)

目录 &#x1f9e0; 前言 &#x1f9fe; 我的需求 &#x1f527; 实现过程&#xff08;按功能一步步来&#xff09; &#x1f6b6;‍♂️ Step 1&#xff1a;基本图像生成界面 &#x1f5c3;️ Step 2&#xff1a;保存图片并显示历史记录 &#x1f4cf; Step 3&#xff1a…...

使用WasmEdge将InternLM集成到Obsidian,打造本地智能笔记助手

本文来自社区投稿&#xff0c;作者Miley Fu&#xff0c;WasmEdge Runtime 创始成员。 本文将介绍如何通过 WasmEdge 将书生浦语&#xff08;InternLM&#xff09;大模型部署在本地&#xff0c;并与 Obsidian 笔记软件集成&#xff0c;从而在笔记软件中直接利用大模型实现文本总…...

深入理解Softmax函数及其在PyTorch中的实现

Softmax函数简介 Softmax函数在机器学习和深度学习中&#xff0c;被广泛用于多分类问题的输出层。它将一个实数向量转换为概率分布&#xff0c;使得每个元素介于0和1之间&#xff0c;且所有元素之和为1。 Softmax函数的定义 给定一个长度为 K K K的输入向量 z [ z 1 , z 2 …...

JGraphT 在 Spring Boot 中的应用实践

1. 引言 1.1 什么是 JGraphT JGraphT 是一个用于处理图数据结构和算法的 Java 库,提供了丰富的图类型和算法实现。 1.2 为什么使用 JGraphT 丰富的图类型:支持简单图、多重图、伪图等多种图类型。强大的算法库:提供最短路径、最小生成树、拓扑排序等多种算法。易于集成:…...

java导入excel更新设备经纬度度数或者度分秒

文章目录 一、背景介绍二、页面效果三、代码0.pom.xml1.ImportDevice.vue2.ImportDeviceError.vue3.system.js4.DeviceManageControl5.DeviceManageUserControl6.Repeater7.FileUtils8.ResponseModel9.EnumLongitudeLatitude10.词条 四、注意点本人其他相关文章链接 一、背景介…...

视频设备轨迹回放平台EasyCVR远程监控体系落地筑牢国土监管防线

一、背景概述 我国土地资源遭违法滥用的现象愈发严峻&#xff0c;各类土地不合理利用问题频发。不当的土地开发不仅加剧了地质危害风险&#xff0c;导致良田受损、森林资源的滥伐&#xff0c;还引发了煤矿无序开采、城市开发区违建等乱象&#xff0c;给国家宝贵的土地资源造成…...

tree-sitter 的 grammar.js 编写方法

tree-sitter 的 grammar.js 编写方法 一、grammar.js 的作用是什么&#xff1f;二、基本结构三、关键词解释四、编写小技巧1. 起点是 source_file2. 所有规则名&#xff08;如 identifier, number&#xff09;都是 $ > ...3. 正则表达式用于定义词法规则&#xff08;终结符&…...

Git 实践笔记

这里写自定义目录标题 一、将当前改动追加到某次commit上二、git 强制修改分支位置 一、将当前改动追加到某次commit上 stash工作区中的当前改动 git stash假设需要修改的commit是 f744c32&#xff0c;将HEAD移动到需要改动的commit的父提交上 git rebase f744c32^ --interact…...

【特权FPGA】之数码管

case语句的用法&#xff1a; 计数器不断的计数&#xff0c;每一个num对应数码管一种数据的输出。实例通俗易懂&#xff0c;一目了然。 timescale 1ns / 1ps// Company: // Engineer: // // Create Date: // Design Name: // Module Name: // Project Name: //…...

Stable Diffusion 四重调参优化——项目学习记录

学习记录还原&#xff1a;在本次实验中&#xff0c;我基于 Stable Diffusion v1.5模型&#xff0c;通过一系列优化方法提升生成图像的质量&#xff0c;最终实现了图像质量的显著提升。实验从基础的 Img2Img 技术入手&#xff0c;逐步推进到参数微调、DreamShaper 模型和 Contro…...

遇到git提交报错:413

是因为提交文件过大导致内存溢出。 解决方法&#xff1a; 假设您的提交历史如下&#xff1a; Apply to .gitignore abcd123 当前提交 efgh456 包含node_modules的提交 ijkl789 较早的正常提交 您可以&#xff1a; 回退到添加node_modules之前的提交&#xff1a; bash App…...

关于nacos注册的服务的ip异常导致网关路由失败的问题

文章目录 关于nacos注册的服务的ip异常导致网关路由失败的问题相关处理方案为方案一:手动指定服务注册的 IP 地址方法二&#xff1a;设置优先使用的网络段方法三&#xff1a;指定网络接口方法四&#xff1a;忽略特定的网卡 备注 关于nacos注册的服务的ip异常导致网关路由失败的…...

大模型在初治CLL成人患者诊疗全流程风险预测与方案制定中的应用研究

目录 一、绪论 1.1 研究背景与意义 1.2 国内外研究现状 1.3 研究目的与内容 二、大模型技术与慢性淋巴细胞白血病相关知识 2.1 大模型技术原理与特点 2.2 慢性淋巴细胞白血病的病理生理与诊疗现状 三、术前风险预测与手术方案制定 3.1 术前数据收集与预处理 3.2 大模…...

【C++游戏引擎开发】第9篇:数学计算库GLM(线性代数)、CGAL(几何计算)的安装与使用指南

写在前面 两天都没手搓实现可用的凸包生成算法相关的代码&#xff0c;自觉无法手搓相关数学库&#xff0c;遂改为使用成熟数学库。 一、GLM库安装与介绍 1.1 vcpkg安装GLM 跨平台C包管理利器vcpkg完全指南 在PowerShell中执行命令&#xff1a; vcpkg install glm# 集成到系…...

408 计算机网络 知识点记忆(8)

前言 本文基于王道考研课程与湖科大计算机网络课程教学内容&#xff0c;系统梳理核心知识记忆点和框架&#xff0c;既为个人复习沉淀思考&#xff0c;亦希望能与同行者互助共进。&#xff08;PS&#xff1a;后续将持续迭代优化细节&#xff09; 往期内容 408 计算机网络 知识…...

基于Python脚本实现Flink on YARN任务批量触发Savepoint的实践指南

基于Python脚本实现Flink on YARN任务批量触发Savepoint的实践指南 一、背景与价值 在流计算生产环境中&#xff0c;Flink on YARN的部署方式凭借其资源管理优势被广泛采用。Savepoint作为Flink任务状态的一致性快照&#xff0c;承载着故障恢复、版本升级、作业暂停等重要场景…...

我可能用到的网站和软件

我可能用到的网站和软件 程序员交流的网站代码管理工具前端组件库前端框架在线工具人工智能问答工具学习的网站Windows系统电脑的常用工具 程序员交流的网站 csdn博客博客园 - 开发者的网上家园InfoQ - 软件开发及相关领域-极客邦掘金 (juejin.cn) 代码管理工具 GitHub 有时…...

FPGA状态机设计:流水灯实现、Modelsim仿真、HDLBits练习

一、状态机思想 1.概念 状态机&#xff08;Finite State Machine, FSM&#xff09;是计算机科学和工程领域中的一种抽象模型&#xff0c;用于描述系统在不同状态之间的转换逻辑。其核心思想是将复杂的行为拆解为有限的状态&#xff0c;并通过事件触发状态间的转移。 2.状态机…...

2024年第十五届蓝桥杯CC++大学A组--成绩统计

2024年第十五届蓝桥杯C&C大学A组--成绩统计 题目&#xff1a; 动态规划&#xff0c; 对于该题&#xff0c;考虑动态规划解法&#xff0c;先取前k个人的成绩计算其方差&#xff0c;并将成绩记录在数组中&#xff0c;记录当前均值&#xff0c;设小蓝已检查前i-1个人的成绩&…...

WinForm真入门(13)——ListBox控件详解

WinForm ListBox 详解与案例 一、核心概念 ‌ListBox‌ 是 Windows 窗体中用于展示可滚动列表项的控件&#xff0c;支持单选或多选操作&#xff0c;适用于需要用户从固定数据集中选择一项或多项的场景‌。 二、核心属性 属性说明‌Items‌管理列表项的集合&#xff0c;支持动…...