Autojs基础-图片处理(images)

0 阅读56分钟

1.前言

图片处理功能是衡量一个脚本的关键因素,我们开发脚本过程中,百分之90的时间都在处理各种图像。几乎脚本每一步都得调用图像处理,每次都要选大量的点。有些小伙伴可能说没必要进行选点啊,可以通过找图的方式来完成图片比对。但是我不推荐找图方式,我开发过程根本不会使用找图的方式来完成图片比对,而是通过找色的方式来完成图片比对。找图的方式缺点如下:第一,找色方式一般不会找过10个点,找色方式点数非常多,找色速度更慢;第二,以模拟器为例,同一模拟器不同机型的颜色都不会相同并且找图偏移量设置不如找色灵活,找图环境兼容性更差;第三,找图会不断的读取本地图片,然后回收图片,回收图片不及时会导致内存积累,找图增加了内存溢出的风险;第四,找图哪怕选图片区域进行查找,也是一块很大的区域,尤其是游戏中页面很容易出现部分遮挡,降低了找图正确率。

有小伙伴可能说,autojs没有自己的截图工具,找色选点非常不方便。我们会使用按键精灵手机助手当作截图工具,然后配合我封装的辅助工具,获取到需要的点颜色信息。

这部分有很多函数标注了Pro 8新增,这些函数无法在免费版中使用。我没有介绍的函数,代表是不存在的。如果存在的函数,但是我不想介绍,会进行说明的。也就是说,没有介绍并且没有说明的函数代表是不存在的。

2.辅助工具

1.概况

辅助工具是通过html代码实现的,我会给出完成代码,请大家复制代码后按照下面结构放置文件。

根文件夹中,需要放置“功能汇总.html”文件和“功能汇总”文件夹,可以参考下图:

在“功能工具”文件中,需要放置“按键精灵横屏坐标转换工具.html”文件和“按键精灵颜色转换工具.html”文件,可以参考下图:

2.html代码

“功能汇总.html”文件代码如下:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>飘逸AutoJs功能工具汇总</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }
        
        body {
            background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);
            min-height: 100vh;
            display: flex;
            flex-direction: column;
            padding: 20px;
        }
        
        .header {
            text-align: center;
            margin-bottom: 40px;
        }
        
        .header h1 {
            font-size: 42px;
            color: white;
            margin-bottom: 15px;
            text-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 15px;
        }
        
        .header p {
            font-size: 18px;
            color: rgba(255, 255, 255, 0.9);
            max-width: 600px;
            margin: 0 auto;
        }
        
        .container {
            width: 100%;
            max-width: 1200px;
            margin: 0 auto;
            flex: 1;
        }
        
        .functions-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
            gap: 25px;
            margin-bottom: 50px;
        }
        
        .function-card {
            background: rgba(255, 255, 255, 0.95);
            border-radius: 20px;
            overflow: hidden;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
            transition: all 0.3s ease;
            display: flex;
            flex-direction: column;
            height: 100%;
        }
        
        .function-card:hover {
            transform: translateY(-5px);
            box-shadow: 0 15px 35px rgba(0, 0, 0, 0.3);
        }
        
        .card-header {
            background: linear-gradient(90deg, #3498db, #2c3e50);
            color: white;
            padding: 20px;
            text-align: center;
        }
        
        .card-header h2 {
            font-size: 22px;
            margin-bottom: 8px;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 10px;
        }
        
        .card-content {
            padding: 25px;
            flex: 1;
            display: flex;
            flex-direction: column;
        }
        
        .card-content p {
            color: #555;
            margin-bottom: 25px;
            line-height: 1.6;
            flex: 1;
        }
        
        .card-actions {
            margin-top: auto;
        }
        
        .goto-button {
            display: block;
            width: 100%;
            padding: 15px;
            background: linear-gradient(90deg, #3498db, #2980b9);
            color: white;
            text-align: center;
            border-radius: 12px;
            text-decoration: none;
            font-weight: 600;
            font-size: 18px;
            transition: all 0.3s ease;
            border: none;
            cursor: pointer;
            box-shadow: 0 5px 15px rgba(52, 152, 219, 0.4);
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 10px;
        }
        
        .goto-button:hover {
            background: linear-gradient(90deg, #2980b9, #2573a7);
            transform: translateY(-3px);
            box-shadow: 0 8px 20px rgba(52, 152, 219, 0.6);
        }
        
        .footer {
            text-align: center;
            padding: 20px;
            color: rgba(255, 255, 255, 0.8);
            font-size: 16px;
            margin-top: 30px;
        }
        
        @media (max-width: 768px) {
            .header h1 {
                font-size: 32px;
            }
            
            .header p {
                font-size: 16px;
            }
            
            .functions-grid {
                grid-template-columns: 1fr;
            }
        }
        
        /* 动画效果 */
        @keyframes fadeIn {
            from { opacity: 0; transform: translateY(20px); }
            to { opacity: 1; transform: translateY(0); }
        }
        
        .function-card {
            animation: fadeIn 0.5s ease forwards;
        }
    </style>
</head>
<body>
    <div class="container">
        <header class="header">
            <h1><i class="fas fa-toolbox"></i>飘逸功能工具汇总</h1>
            <p>集中管理各种实用工具,点击下方卡片即可使用相应功能</p>
        </header>
        
        <main>
            <div class="functions-grid" id="functionsContainer">
                <!-- 功能卡片将通过JavaScript动态生成 -->
            </div>
        </main>
    </div>
    
    <footer class="footer">
        <p>© 2025 飘逸功能工具汇总 | 为您提供便捷的AutoJs工具集合</p>
    </footer>

    <script>
        // 功能数据数组 - 核心配置区:只需在此添加新功能即可自动显示在页面上
        const functions = [
            {
                title: "横屏坐标转换工具",
                icon: "fa-map-marked-alt",
                description: "将按键精灵的横屏坐标,转换为autojs的横屏坐标",
                link: "功能工具/按键精灵横屏坐标转换工具.html", // 您的坐标转换工具页面
                buttonText: "坐标转换"
            },
            {
                title: "颜色信息转换工具",
                icon: "fa-palette",
                description: "将按键精灵点颜色信息,转换为autojs点颜色信息",
                link: "功能工具/按键精灵颜色转换工具.html",
                buttonText: "颜色转换"
            },
            // {
            //     title: "计算器",
            //     icon: "fa-calculator",
            //     description: "功能强大的科学计算器,支持基本运算和高级函数计算。",
            //     link: "calculator.html",
            //     buttonText: "开始计算"
            // },
            // {
            //     title: "单位转换器",
            //     icon: "fa-exchange-alt",
            //     description: "长度、重量、温度等多种单位之间的快速转换工具。",
            //     link: "unit_converter.html",
            //     buttonText: "转换单位"
            // }
            // 要添加新功能,只需在此处继续添加新的功能对象即可
            // 页面会自动排列新增的功能模块
        ];

        document.addEventListener('DOMContentLoaded', function() {
            const container = document.getElementById('functionsContainer');
            
            // 动态生成功能卡片
            functions.forEach(func => {
                const card = document.createElement('div');
                card.className = 'function-card';
                
                card.innerHTML = `
                    <div class="card-header">
                        <h2><i class="fas ${func.icon}"></i> ${func.title}</h2>
                    </div>
                    <div class="card-content">
                        <p>${func.description}</p>
                        <div class="card-actions">
                            <a href="${func.link}" target="_blank" class="goto-button">
                                <i class="fas fa-arrow-right"></i> ${func.buttonText}
                            </a>
                        </div>
                    </div>
                `;
                
                container.appendChild(card);
            });
        });

        // 添加新功能的函数(编程方式扩展)
        function addNewFunction(newFunction) {
            functions.push(newFunction);
            
            const container = document.getElementById('functionsContainer');
            const card = document.createElement('div');
            card.className = 'function-card';
            card.style.opacity = '0';
            
            card.innerHTML = `
                <div class="card-header">
                    <h2><i class="fas ${newFunction.icon}"></i> ${newFunction.title}</h2>
                </div>
                <div class="card-content">
                    <p>${newFunction.description}</p>
                    <div class="card-actions">
                        <a href="${newFunction.link}" target="_blank" class="goto-button">
                            <i class="fas fa-arrow-right"></i> ${newFunction.buttonText}
                        </a>
                    </div>
                </div>
            `;
            
            container.appendChild(card);
            
            // 触发动画
            setTimeout(() => {
                card.style.opacity = '1';
                card.style.transform = 'translateY(0)';
            }, 10);
        }
    </script>
</body>
</html>

“按键精灵横屏坐标转换工具.html”文件代码如下:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>坐标转换工具</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }
        
        body {
            background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            padding: 20px;
        }
        
        .container {
            width: 100%;
            max-width: 500px;
            background: rgba(255, 255, 255, 0.95);
            border-radius: 20px;
            box-shadow: 0 15px 35px rgba(0, 0, 0, 0.5);
            overflow: hidden;
        }
        
        .header {
            background: linear-gradient(90deg, #3498db, #2c3e50);
            color: white;
            padding: 20px;
            text-align: center;
        }
        
        .header h1 {
            font-size: 28px;
            margin-bottom: 5px;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 10px;
        }
        
        .header p {
            font-size: 16px;
            opacity: 0.9;
        }
        
        .content {
            padding: 30px;
        }
        
        .input-group {
            margin-bottom: 25px;
            position: relative;
        }
        
        .input-group label {
            display: block;
            margin-bottom: 10px;
            font-weight: 600;
            color: #2c3e50;
            font-size: 16px;
            display: flex;
            align-items: center;
            gap: 8px;
        }
        
        .input-icon {
            position: absolute;
            left: 15px;
            top: 43px;
            color: #3498db;
            font-size: 18px;
            z-index: 2;
        }
        
        input {
            width: 100%;
            padding: 15px 15px 15px 45px;
            border: 2px solid #e0e0e0;
            border-radius: 12px;
            font-size: 18px;
            transition: all 0.3s ease;
            background-color: #fff;
            color: #333;
            position: relative;
        }
        
        input:focus {
            outline: none;
            border-color: #3498db;
            box-shadow: 0 5px 15px rgba(52, 152, 219, 0.25);
        }
        
        #coordinateInput::placeholder {
            color: #95a5a6;
            font-style: italic;
        }
        
        .output-group {
            margin-top: 35px;
        }
        
        .output-group label {
            display: block;
            margin-bottom: 10px;
            font-weight: 600;
            color: #2c3e50;
            font-size: 16px;
            display: flex;
            align-items: center;
            gap: 8px;
        }
        
        #result {
            background-color: #e8f4fc;
            font-weight: bold;
            color: #2980b9;
            padding-left: 45px;
        }
        
        .button-group {
            margin-top: 30px;
            display: flex;
            gap: 15px;
        }
        
        button {
            flex: 1;
            padding: 15px;
            border: none;
            border-radius: 12px;
            font-size: 18px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s ease;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 10px;
        }
        
        #convertBtn {
            background: linear-gradient(90deg, #3498db, #2980b9);
            color: white;
            box-shadow: 0 5px 15px rgba(52, 152, 219, 0.4);
        }
        
        #convertBtn:hover {
            background: linear-gradient(90deg, #2980b9, #2573a7);
            transform: translateY(-3px);
            box-shadow: 0 8px 20px rgba(52, 152, 219, 0.6);
        }
        
        #resetBtn {
            background: #f1f2f6;
            color: #7f8c8d;
            border: 2px solid #e0e0e0;
        }
        
        #resetBtn:hover {
            background: #e0e0e0;
            transform: translateY(-3px);
        }
        
        .instructions {
            background: #fff9e6;
            border-left: 4px solid #f39c12;
            padding: 15px;
            border-radius: 0 8px 8px 0;
            margin-top: 25px;
            font-size: 14px;
            color: #7d6608;
        }
        
        .instructions h3 {
            margin-bottom: 8px;
            display: flex;
            align-items: center;
            gap: 8px;
        }
        
        .instructions ul {
            padding-left: 20px;
        }
        
        .instructions li {
            margin-bottom: 5px;
        }
        
        .example {
            font-family: monospace;
            background: #f4f4f4;
            padding: 3px 6px;
            border-radius: 4px;
            margin: 0 2px;
        }
        
        .footer {
            text-align: center;
            padding: 20px;
            color: #7f8c8d;
            font-size: 14px;
            border-top: 1px solid #ecf0f1;
        }
        
        @media (max-width: 500px) {
            .button-group {
                flex-direction: column;
            }
            
            .header h1 {
                font-size: 24px;
            }
            
            input {
                padding: 12px 12px 12px 40px;
                font-size: 16px;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1><i class="fas fa-map-marked-alt"></i> 坐标转换工具</h1>
            <p>输入坐标和屏幕宽度,自动进行坐标转换</p>
        </div>
        
        <div class="content">
            <div class="input-group">
                <label for="coordinateInput"><i class="fas fa-map-pin"></i> 坐标输入 (格式:x,y)</label>
                <div class="input-icon">
                    <i class="fas fa-location-dot"></i>
                </div>
                <input type="text" id="coordinateInput" placeholder="例如: 407,1002" value="407,1002">
            </div>
            
            <div class="input-group">
                <label for="numberInput"><i class="fas fa-calculator"></i> 屏幕宽度</label>
                <div class="input-icon">
                    <i class="fas fa-hashtag"></i>
                </div>
                <input type="number" id="numberInput" value="720">
            </div>
            
            <div class="output-group">
                <label for="result"><i class="fas fa-arrow-right"></i> 转换结果</label>
                <div class="input-icon">
                    <i class="fas fa-code"></i>
                </div>
                <input type="text" id="result" readonly>
            </div>
            
            <div class="button-group">
                <button id="convertBtn">
                    <i class="fas fa-sync-alt"></i> 转换坐标
                </button>
                <button id="resetBtn">
                    <i class="fas fa-undo"></i> 重置
                </button>
            </div>
            
            <div class="instructions">
                <h3><i class="fas fa-info-circle"></i> 使用说明</h3>
                <ul>
                    <li>在第一个输入框中输入坐标,格式为 <span class="example">x,y</span>(例如: <span class="example">407,1002</span></li>
                    <li>在第二个输入框中输入参考数值(默认720)</li>
                    <li>点击"转换坐标"按钮进行计算</li>
                    <li>转换过程:交换坐标 → 屏幕宽度减去纵坐标 → 输出结果</li>
                    <li>示例:输入"407,1002" → 交换后为"1002,407" → 720-407=313 → 输出"1002,313"</li>
                </ul>
            </div>
        </div>
        
        <!-- <div class="footer">
            <p>© 2023 坐标转换工具 | 设计美观实用的转换界面</p>
        </div> -->
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', function() {
            const coordInput = document.getElementById('coordinateInput');
            const numInput = document.getElementById('numberInput');
            const resultOutput = document.getElementById('result');
            const convertBtn = document.getElementById('convertBtn');
            const resetBtn = document.getElementById('resetBtn');
            
            // 转换坐标功能
            convertBtn.addEventListener('click', function() {
                // 获取坐标输入值
                const coordValue = coordInput.value.trim();
                
                // 验证输入格式
                if (!coordValue || !coordValue.includes(',')) {
                    alert('请输入有效的坐标格式,如:"407,1002"');
                    coordInput.focus();
                    return;
                }
                
                // 分割坐标值
                const parts = coordValue.split(',');
                const x = parseFloat(parts[0].trim());
                const y = parseFloat(parts[1].trim());
                
                // 验证坐标值
                if (isNaN(x) || isNaN(y)) {
                    alert('坐标值必须是有效的数字');
                    coordInput.focus();
                    return;
                }
                
                // 获取屏幕宽度
                const refValue = parseFloat(numInput.value);
                
                // 执行转换:交换坐标,然后用屏幕宽度减去纵坐标
                const newY = refValue - x;
                const result = `${y}, ${newY}`;
                
                // 显示结果
                resultOutput.value = result;
                
                // 添加动画效果
                resultOutput.style.animation = 'none';
                setTimeout(() => {
                    resultOutput.style.animation = 'pulse 0.5s';
                }, 10);
            });
            
            // 重置功能
            resetBtn.addEventListener('click', function() {
                coordInput.value = '';
                numInput.value = '720';
                resultOutput.value = '';
                coordInput.focus();
            });
            
            // 添加样式动画
            const style = document.createElement('style');
            style.textContent = `
                @keyframes pulse {
                    0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(52, 152, 219, 0.7); }
                    70% { transform: scale(1.02); box-shadow: 0 0 0 10px rgba(52, 152, 219, 0); }
                    100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(52, 152, 219, 0); }
                }
            `;
            document.head.appendChild(style);
            
            // 初始加载时执行一次转换
            setTimeout(() => {
                convertBtn.click();
            }, 500);
        });
    </script>
</body>
</html>

“按键精灵颜色转换工具.html”文件代码如下:

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>颜色信息转换工具</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }

        body {
            background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            padding: 20px;
            overflow-x: hidden;
        }

        .container {
            width: 100%;
            max-width: 700px;
            background: rgba(255, 255, 255, 0.95);
            border-radius: 20px;
            box-shadow: 0 15px 35px rgba(0, 0, 0, 0.5);
            overflow: hidden;
        }

        .header {
            background: linear-gradient(90deg, #3498db, #2c3e50);
            color: white;
            padding: 20px;
            text-align: center;
        }

        .header h1 {
            font-size: 24px;
            margin-bottom: 5px;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 10px;
        }

        .header p {
            font-size: 14px;
            opacity: 0.9;
        }

        .content {
            padding: 20px;
            display: flex;
            flex-direction: column;
            gap: 20px;
        }

        .panel {
            background: #f8f9fa;
            border-radius: 12px;
            padding: 20px;
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
            width: 100%;
        }

        .input-group {
            margin-bottom: 20px;
            position: relative;
        }

        .input-group label {
            display: block;
            margin-bottom: 8px;
            font-weight: 600;
            color: #2c3e50;
            font-size: 14px;
            display: flex;
            align-items: center;
            gap: 8px;
        }

        .input-icon {
            position: absolute;
            left: 12px;
            top: 40px;
            color: #3498db;
            font-size: 16px;
            z-index: 2;
        }

        input,
        textarea,
        select {
            width: 100%;
            padding: 10px 10px 10px 40px;
            border: 2px solid #e0e0e0;
            border-radius: 8px;
            font-size: 14px;
            transition: all 0.3s ease;
            background-color: #fff;
            color: #333;
            position: relative;
        }

        textarea {
            min-height: 100px;
            font-family: monospace;
            resize: vertical;
            padding-left: 12px;
        }

        input:focus,
        textarea:focus {
            outline: none;
            border-color: #3498db;
            box-shadow: 0 5px 15px rgba(52, 152, 219, 0.25);
        }

        .checkbox-wrapper {
            display: flex;
            flex-wrap: wrap;
            gap: 15px;
            margin: 15px 0;
            justify-content: flex-start;
            /* 左对齐而不是居中 */
        }

        .checkbox-container {
            display: flex;
            align-items: center;
            margin-bottom: 10px;
            cursor: pointer;
        }

        .checkbox-container input[type="checkbox"] {
            display: none;
        }

        .checkmark {
            width: 18px;
            height: 18px;
            background: #fff;
            border: 2px solid #3498db;
            border-radius: 4px;
            margin-right: 8px;
            position: relative;
            transition: all .3s ease;
        }

        .checkbox-container input[type="checkbox"]:checked+.checkmark {
            background: #3498db;
            border-color: #3498db;
        }

        .checkbox-container input[type="checkbox"]:checked+.checkmark:after {
            content: "";
            position: absolute;
            left: 5px;
            top: 2px;
            width: 5px;
            height: 9px;
            border: solid white;
            border-width: 0 2px 2px 0;
            transform: rotate(45deg);
        }

        .output-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 10px;
        }

        .output-header label {
            font-weight: 600;
            color: #2c3e50;
            font-size: 14px;
            display: flex;
            align-items: center;
            gap: 8px;
        }

        #result {
            background-color: #e8f4fc;
            font-weight: bold;
            color: #2980b9;
            padding-left: 12px;
            min-height: 150px;
            font-family: monospace;
            white-space: pre-wrap;
            font-size: 13px;
        }

        .button-group {
            margin-top: 20px;
            display: flex;
            gap: 10px;
        }

        button {
            flex: 1;
            padding: 10px 15px;
            border: none;
            border-radius: 8px;
            font-size: 14px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s ease;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
        }

        #convertBtn {
            background: linear-gradient(90deg, #3498db, #2980b9);
            color: white;
            box-shadow: 0 5px 15px rgba(52, 152, 219, 0.4);
        }

        #convertBtn:hover {
            background: linear-gradient(90deg, #2980b9, #2573a7);
            transform: translateY(-2px);
            box-shadow: 0 8px 20px rgba(52, 152, 219, 0.6);
        }

        #resetBtn {
            background: #f1f2f6;
            color: #7f8c8d;
            border: 2px solid #e0e0e0;
        }

        #resetBtn:hover {
            background: #e0e0e0;
            transform: translateY(-2px);
        }

        #copyBtn {
            background: linear-gradient(90deg, #4caf50, #2e7d32);
            color: #fff;
            flex: 0 0 auto;
            padding: 8px 12px;
            font-size: 13px;
        }

        #copyBtn:hover {
            background: linear-gradient(90deg, #2e7d32, #1b5e20);
            transform: translateY(-2px);
        }

        .success-message {
            position: fixed;
            top: 20px;
            right: 20px;
            background: #4caf50;
            color: #fff;
            padding: 12px 20px;
            border-radius: 8px;
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
            display: flex;
            align-items: center;
            gap: 10px;
            transform: translateX(150%);
            transition: transform 0.4s ease-in-out;
            z-index: 1000;
            font-size: 14px;
            font-weight: 600;
        }

        .success-message.show {
            transform: translateX(0);
        }

        @media (max-width: 768px) {
            .container {
                max-width: 100%;
                margin: 10px;
            }

            .content {
                padding: 15px;
            }

            .button-group {
                flex-direction: column;
            }

            .header h1 {
                font-size: 20px;
            }

            input,
            textarea {
                padding: 8px 8px 8px 35px;
                font-size: 14px;
            }

            .checkbox-wrapper {
                gap: 10px;
            }

            .success-message {
                top: 10px;
                right: 10px;
                left: 10px;
                transform: translateY(-100px);
            }

            .success-message.show {
                transform: translateY(0);
            }
        }

        /* 滚动条样式 */
        ::-webkit-scrollbar {
            width: 6px;
        }

        ::-webkit-scrollbar-track {
            background: #f1f1f1;
            border-radius: 10px;
        }

        ::-webkit-scrollbar-thumb {
            background: #c1c1c1;
            border-radius: 10px;
        }

        ::-webkit-scrollbar-thumb:hover {
            background: #a8a8a8;
        }
    </style>

</head>

<body>
    <div class="container">
        <div class="header">
            <h1><i class="fas fa-sync-alt"></i> 颜色信息转换工具</h1>
            <p>根据颜色信息转换为多点比色或多点找色信息</p>
        </div>

        <div class="content">
            <div class="panel">
                <div class="input-group">
                    <label for="kjlInput"><i class="fas fa-palette"></i> 按键精灵颜色数据</label>
                    <textarea id="kjlInput" placeholder="格式: x|y|颜色值,x|y|颜色值..."></textarea>
                </div>

                <div class="input-group">
                    <label for="widthInput"><i class="fas fa-arrows-alt-h"></i> 屏幕宽度</label>
                    <input type="number" id="widthInput" value="720">
                </div>

                <div class="checkbox-wrapper">
                    <label class="checkbox-container">
                        <input type="checkbox" id="landscapeCheckbox" checked>
                        <span class="checkmark"></span>
                        <span class="checkbox-label">启用横屏坐标转换</span>
                    </label>

                    <label class="checkbox-container">
                        <input type="checkbox" id="findRegionColorCheckbox">
                        <span class="checkmark"></span>
                        <span class="checkbox-label">多点找色</span>
                    </label>
                </div>

                <div class="input-group">
                    <label for="widthOffset"><i class="fas fa-arrow-right"></i>屏幕宽度偏移量</label>
                    <input type="number" id="widthOffset" value="-1">
                </div>

                <div class="button-group">
                    <button id="convertBtn"><i class="fas fa-sync-alt"></i> 转换</button>
                    <button id="resetBtn"><i class="fas fa-undo"></i> 重置</button>
                </div>

                <div class="panel">
                    <div class="output-header">
                        <label for="result"><i class="fas fa-arrow-right"></i> 转换结果</label>
                        <div>
                            <button id="copyBtn"><i class="fas fa-copy"></i> 复制结果</button>
                        </div>
                    </div>
                    <textarea id="result" readonly></textarea>
                </div>

            </div>

        </div>
    </div>

    <div class="success-message" id="copySuccess"><i class="fas fa-check-circle"></i><span>已复制到剪贴板</span></div>

    <script>
        function bgrStrToRgbArr(bgrStr) {
            // bgrStr 例如 51D9F5(B G R)
            const b = parseInt(bgrStr.substring(0, 2), 16) || 0;
            const g = parseInt(bgrStr.substring(2, 4), 16) || 0;
            const r = parseInt(bgrStr.substring(4, 6), 16) || 0;
            return [r, g, b];
        }

        function rgbToHex(arr) { return `#${arr[0].toString(16).padStart(2, '0')}${arr[1].toString(16).padStart(2, '0')}${arr[2].toString(16).padStart(2, '0')}`.toLowerCase(); }

        // 使用训练模型转换单个 bgrStr -> hex
        function convertWithModel(bgrStr) {
            const inRgb = bgrStrToRgbArr(bgrStr);
            return rgbToHex(inRgb);
        }

        // ---- DOM 逻辑 ----
        document.addEventListener('DOMContentLoaded', () => {
            const kjlInput = document.getElementById('kjlInput');
            const widthInput = document.getElementById('widthInput');
            const landscapeCheckbox = document.getElementById('landscapeCheckbox');
            const findRegionColorCheckbox = document.getElementById('findRegionColorCheckbox');
            const widthOffset = document.getElementById('widthOffset');
            const convertBtn = document.getElementById('convertBtn');
            const resetBtn = document.getElementById('resetBtn');
            const copyBtn = document.getElementById('copyBtn');
            const resultOutput = document.getElementById('result');
            const copySuccess = document.getElementById('copySuccess');

            function parseExpectedInput(input) {
                try {
                    const cleaned = input.replace(/\s/g, '');
                    const regex = /[(\d+),(\d+),'#?([0-9a-fA-F]{6})']/g;
                    const points = []; let m;
                    while ((m = regex.exec(cleaned)) !== null) {
                        points.push({ x: parseInt(m[1]), y: parseInt(m[2]), color: '#' + m[3].toLowerCase() });
                    }
                    return points;
                } catch (e) { return []; }
            }



            // 转换并分析(不自动训练,除非勾选 autoTrainCheckbox)
            convertBtn.addEventListener('click', () => {
                try {
                    let kjlValue = kjlInput.value.trim(); if (kjlValue.indexOf('"') != -1) kjlValue = kjlValue.replace(/"/g, '');
                    if (!kjlValue) { alert('请输入按键精灵数据'); return; }
                    const screenWidth = parseFloat(widthInput.value);
                    const screenWidthOffset = parseFloat(widthOffset.value);
                    if (isNaN(screenWidth)) { alert('请输入有效屏幕宽度'); return; }
                    const isLandscape = landscapeCheckbox.checked;

                    // 解析按键精灵数据并转换
                    const pts = kjlValue.split(',').map(s => s.trim()).filter(Boolean);
                    const converted = [];
                    for (const p of pts) {
                        const parts = p.split('|').map(x => x.trim());
                        if (parts.length !== 3) continue;
                        const x = parseInt(parts[0]), y = parseInt(parts[1]), col = parts[2];
                        if (isNaN(x) || isNaN(y) || !col) continue;
                        let cx, cy;
                        if (isLandscape) { cx = y; cy = screenWidth - x + screenWidthOffset; } else { cx = x + screenWidthOffset; cy = y; }
                        const hex = convertWithModel(col);
                        converted.push([cx, cy, hex, col]);
                    }

                    // 输出格式化
                    let out;
                    // 如果需要多点找色
                    if (findRegionColorCheckbox.checked) {
                        out = `"${converted[0][2]}", [`;
                        const firstX = converted[0][0];
                        const firstY = converted[0][1];
                        for (let i = 1; i < converted.length; i++) {
                            out += `[${converted[i][0] - firstX}, ${converted[i][1] - firstY}, "${converted[i][2]}"]`;
                            if (i < converted.length - 1) out += ', ';
                        }
                        out += "]"
                    } else {
                        out = '[';
                        for (let i = 0; i < converted.length; i++) {
                            out += `[${converted[i][0]}, ${converted[i][1]}, "${converted[i][2]}"]`;
                            if (i < converted.length - 1) out += ', ';
                        }
                        out += ']';
                    }

                    resultOutput.value = out;



                } catch (err) { alert('转换出错: ' + err.message); console.error(err); }
            });

            // 复制结果
            copyBtn.addEventListener('click', () => { resultOutput.select(); document.execCommand('copy'); showCopy(); });

            // 重置
            resetBtn.addEventListener('click', () => {
                kjlInput.value = '';
                widthInput.value = '720';
                landscapeCheckbox.checked = true;
                resultOutput.value = '';
            });

            function showCopy() { copySuccess.classList.add('show'); setTimeout(() => copySuccess.classList.remove('show'), 1600); }
        });

    </script>
</body>

</html>

3.功能汇总

用任意浏览器打开“功能汇总.html”文件。

“功能汇总”通过名字就可以看出,用于汇总功能的。我是考虑到可能有各种工具,通过这个页面统一管理,能够在这个页面跳转到需要的功能。同时,如果有新工具,可以放在“功能汇总”文件夹,然后在“功能汇总.html”中添加工具名称,会自动显示新功能。可以根据情况跳转到对应功能,点击位置如下:

4.截图工具

我们使用的截屏工具是按键精灵手机助手,如果没有安装可以前往按键精灵官网下载。请选择“按键精灵手机助手”的下载链接。为了方便,下面都会以“按键助手”来代表“按键精灵手机助手”。

同时下载按键精灵安卓版,安装到需要连接的设备中,大部分情况下,我们安装到模拟器即可。安装成功后,需要在模拟器打开按键精灵应用。

如果我们先启动的是模拟器,再启动按键助手,助手会自动连接(有概率自动连接失败,需要自己手动连接)。如果先启动按键助手,需要手动连接下模拟器。点击“尚未连接到手机”,再点击“重新扫描”,扫描到模拟器设备,然后点击即可,等待连接成功。

我是为了测试版本autojs兼容性,启动了两个版本的雷电模拟器,才会显示两个,正常只显示一个设备。连接设备时,有个“扫描全网段”这个功能,这个功能用于手机真机或者手机虚拟机连接wifi后,同时wifi路由器连接网线和启动按键精灵手机助手的电脑网线不是同一根,但是再同一个内网时进行勾选。这个功能作用就是扫描内网中,所有启动按键精灵的设备。

连接成功后,点击“抓抓”,会弹出抓抓助手。

我们点击截图后,会截取连接设备当前页面。

我们找到要进行颜色比对的点,然后鼠标右键,加入到点1、点2...由于我一截图,弹窗就消失,就不截图了。选择点后,右侧会显示点颜色信息。

需要特别注意,横屏截图时,需要将图片通过“左旋转90度”或“右旋转90度”调正后,再进行选点。下面转换工具,都代表在调正情况下的转换信息。

横屏状态下,截图默认也是竖屏。

一般情况下,点击一下“向左旋转90度”就会调正,不管怎样,一定保证图片调正后,再进行选点。

我们只需要会使用多点比色功能即可,截图后如果不放心是否能够识别。可以选择“多点比色”,然后点击“开始测试”按钮。

结果如果显示“1”代表找到,如果显示“0”代表没有找到。这种测试,必须保证设备当前页面中能够找到,并不是从截图中查找。这种方式,一般是固定页面或者选点不确定时,简单验证下。对于游戏中变化比较快的页面,在测试时,设备已经不是那个页面了,很难再测试。不过,大家不要担心,只要你选的截图和选点没问题,不用测试也不会影响最后的功能。

最后,需要告诉大家,选点时,最好选那种上下左右偏移一个单位颜色都差距不大的点,也就是最好选择点的中心。也就是最好选择相同颜色区域较大的点,并且尽量保证这个点居中。这是理想状态,如果没有这种点,颜色区域都比较细,并不代表就不能选,只是大区域点优先级更高。为什么要这样?如果你开发多个设备,哪怕统一分辨率情况下,有概率设备宽度差1。选这种中心点,是为了提高兼容性。有人开发的脚本兼容性很好,有人开发的脚本不同设备还得进行微调,有人开发的基本不同设备需要大调,这是平时开发细节处理不同而导致的结果。

4.坐标转换

在坐标操作部分,我已经介绍了无障碍操作和RootAutomator操作坐标信息。大家一定记住坐标转换基于横屏状,竖屏状态下所有操作的坐标信息都是一致的,下面就不会说横屏这个前提,大家知道就行。无障碍操作的坐标信息和按键助手不同,当然需要坐标转化。RootAutomator操作的坐标信息和按键助手相同,但是用的比较少。

选择“横屏坐标转换工具”。

上面的使用说明已经介绍很具体了,并且给出了例子。因为,我平时习惯使用720*1280分辨率的设备,宽度值默认为720。

截图工具中,已经介绍如何选点。这个工具主要作用是点击坐标转换,也就是点击某个位置时,用于单点转换。如果需要多点颜色信息转换,会使用后面的颜色信息转换工具。如果我们想点击某个位置,可以在按键助手抓抓上截图,找到需要点击的位置,右键加入点,然后找到坐标那一栏复制,然后粘贴到坐标输入位置。

也就是说,在720宽度下,按键精灵678,41坐标转换为autojs的坐标是41, 42。为什么我直接说autojs的坐标?不是说RootAutomator操作的坐标信息和按键助手一致,两者坐标信息确实一致。因为RootAutomator操作平时不怎么用,并且用的比较多的无障碍操作以及图像处理的坐标信息是一致的,都需要进行转换使用。连图像处理都使用和无障碍操作一样的坐标信息,应该能够看出autojs更推荐这种坐标信息,我以后默认转换后的坐标信息为autojs坐标,大家清楚里面的关系即可。

5.颜色转换

选择“颜色信息转换工具”。

下面我会给大家详细介绍下这个转换工具的功能,可以分为七大功能。

功能一-按键精灵颜色数据:此内容来自按键助手抓抓中多点比色的“颜色描述”。如果我们选好要比对的点,可以找到这个位置,点一下“颜色描述”后面的“复制”按钮,然后粘贴到工具这个位置。

功能二-屏幕宽度:这个值和坐标转换的值功能和用法完全一致。这个值是屏幕宽度,因为我喜欢使用720*1280的分辨率,因此默认为720。

功能三-启动横屏坐标转换:在横屏状态下,并且需要将颜色信息中每个点按照坐标转换工具的方式进行转换时开启,默认开启。

功能四-多点找色:从多点比色功能转换为多点找色功能,默认关闭。多点找色在点固定时使用,做到每个点颜色信息一一比对;多点找色在某个区域内,按照颜色相对信息找到某个点。这两个功能和按键精灵中的对应功能作用完全一致。

功能五-屏幕宽度偏移量:用于校对autojs和截图软件之间宽度方向的坐标差距,由按键助手坐标转换为autojs坐标,需要设为-1,默认也是-1。只有“启动横屏坐标转换”功能时,这个值才会生效。

功能六-转换与重置:转换按钮用于数据转换,重置按钮用于重置输入框数据,但是屏幕宽度偏移量不会重置。

功能七-复制结果:用于复制转换的结果。

如果不勾选“多点找色”,代表使用多点找色功能,会转换为一个点颜色数组,可以直接复制到对应函数中使用。

勾选“多点找色”,代表切换为多点找色功能,会转换为初始点颜色和相对点颜色数组,后面也可以直接复制到对应函数中使用。

3.截屏函数

1.概况

所有函数进行了全局保存,可以通过“images.”调用可以,也可以直接调用。对于requestScreenCapture函数,我更推荐直接调用的方式。对于captureScreen函数,我更推荐“images.”这种调用方式。调用方式纯属个人习惯,除了requestScreenCapture函数外,图像处理模块全局保存的函数也习惯“images.”这种调用方式。这部分函数包括截屏权限请求函数和截屏函数。

2.requestScreenCapture

请求截图权限。通过官方文档看到,可以传递一个boolean类型的参数,甚至传递对象类型的配置参数。boolean没必要传递,对象方式免费版不支持。大家只需要记住,用这个函数时,不用传递参数。第一次请求截图权限时,或者没有设置永久允许权限,需要手动允许权限才能截图。因此,请求截图权限后,最好加一个自己认为可以的延迟。真正脚本开发过程中,可以考虑超时重复获取权限的情况,不启动这个权限,无法进行截图的。

注意:

雷电模拟器4的自动横屏有问题,调用requestScreenCapture后,横屏应用也会变成竖屏。这个问题是雷电模拟器4的原因,在虚拟机和雷电模拟器最新版中都不存在。强制竖屏后,对脚本执行没有影响,但是我们看起来很怪。这个问题是雷电模拟器4的原因,和脚本没有关系,横屏应用我一般在虚拟机中开发的比较多,根本不会出现这个问题。实现不行用雷电模拟器9,只是个别函数用不了罢了,不影响大局。
我尝试了别的android 7的模拟器也会存在这个问题,这个问题不是换模拟器能解决的。不过,android 7的模拟器一般都会有root权限,我们可以通过root方式进行截图使用。这个问题并不是所有android 7的系统都会出现,在虚拟机中就不会出现。我考虑了下,是不是由于系统架构原因?大部分模拟器都是x86,而虚拟机是arm架构。通过下面截图,是将图片保存到本地,然后再加载图片后,返回图片。这个图片需要我们手动回收,不然会导致内存溢出。

使用下面截图方式之前,请提前请求root权限。说实话,这部分内容我是将整个基础部分内容完成后又回来写的,如果后面内容还有推荐模拟器的情况,请忽略!我写这部分内容时,我已经在插件中做好了功能。我封装了个初始化截图权限的函数,能够根据系统架构请求不同权限(root权限或截图权限)。然后再调用截图命令时,会返回图片,哪怕环境需要shell命令截图,默认也会自动回收图片。这两个函数在雷电模拟器4(android 7)、雷电模拟器9(android 9)以及VMOS Pro虚拟机(android 7)都适配,我推荐大家选这个三个环境开发。shell截图函数我在别的模拟器中也用过,有时候会花屏,大家最好在雷电模拟器中使用。

// 定义一个使用 shell 命令截图的函数
function captureScreenWithShell(savePath) {
    // 执行系统截图命令,-p 参数表示生成 PNG 格式图片
    let result = shell("screencap -p " + savePath, true);
    if (result.code === 0) {
        // 命令成功执行,读取图片文件为 Auto.js 的 Image 对象
        let img = images.read(savePath);
        return img;
    } else {
        toastLog("截图失败,请确保已授予 root 权限");
        return null;
    }
}

3.captureScreen

截取屏幕。这个函数可以传参数,也可以不传递参数。

传递参数时,将截图保存到本地,需要传递文件绝对路径一个参数,参数类型为字符串。

不传递参数时,将截图数据返回,返回类型为图片,这个数据类型不用管,直接传递到接收图片类型数据的函数中就能使用。同时,这个图片数据不用手动回收,后面系统会自动回收。

由于我应用已经永久获取了截取权限,为了提高脚本执行速度,我就不加延迟了。

// 请求截图权限
requestScreenCapture();

// 将截图保存到本地
let fileRootPath = "/storage/emulated/0/com.py.test/";
images.captureScreen(fileRootPath + "test.png");

// 获取截图类型
let img = images.captureScreen();
console.log(typeof img);

注意:

captureScreen函数页面不刷新,不会执行。我这么说,有些小伙伴可能不理解。比如,在一个不刷新的页面,连续调用两次截图函数,因为页面没有刷新,会在第二个函数卡死。两个页面有轻微不同,都算是页面刷新了。在现在游戏中,各种滚屏特效,页面会一直有变化,根本没有卡死一说。

我介绍这个情况,是页面变动不大的脚本中,脚本出现了特殊情况,页面不刷新,又再次调用了截图函数,脚本会直接卡死。脚本一般不会出现特殊情况,在云手机性能比较差的机型中,是有概率出现这种情况的。我当初写的游戏,没有什么特效,并且人数又少,我又用了低性能的云手机,有概率出现特殊情况的。

解决这个问题的思路很简单,就是容易因为页面不刷新导致开始的位置,通过脚本使页面刷新,这样不就不会卡死了嘛。但是,脚本卡死了,后面脚本又不执行,如果在前面执行又起不到关键位置刷新的情况。解决这个问题也是有办法的,可以在脚本开头开启个线程,会和主线程并行进行,不会出现卡顿了。但是,有小伙伴觉得,我只是需要在有概率卡顿的位置开启页面刷新,解决这个问题,我们可以通过一个是否开启刷新这个变量控制,在需要刷新的地方设置为true,结束后再设置为false。

解决截图卡顿问题的大致思路想好了,但是通过什么来控制页面刷新呢?我看到有的博主也考虑到这个问题,他们解决办法是控制一个小标签的显示和隐藏,比如在线程中一直控制小标签的显示和隐藏,在中间再加个合适的延迟,这样就完成了页面刷新。在脚本讲解过程中,小伙伴们会发现挺好用的,但是,我猜测那个小标签喜欢放在右上角等不明显位置,大概率是使用的autojs的悬浮窗功能。autojs悬浮窗功能有个特点,所谓的显示隐藏,实际上是创建然后销毁的过程,并不是创建一个悬浮窗然后通过某个值控制显示隐藏。这样不断的创建和销毁,会不断产生内存积累,最后导致内存溢出。

大家也不用担心,悬浮窗的内存积累是比较少的,我们用来配置脚本信息产生的内存积累可以忽略不计。但是,使用刷新的地方,会非常品频繁,甚至一秒创建和销毁好几次,积少成多才导致内存溢出。我使用刷新方式就是toast方式,通过toast提示信息的方式实现页面刷新。这种方式是不会产生内存溢出的,我肯定在脚本开发过程中使用过才会推荐给大家,有足够运行时间的脚本作为支撑,而不是仅执行过几次就推荐。

下面代码中详细介绍了如何实现页面刷新以及使用方式。线程后面会介绍,这里只知道这样会实现页面刷新即可。通过这种方式,开启一个页面刷新的线程,需要刷新的地方设置hasOpenRefreshPage为true即可,想关闭页面刷新设置hasOpenRefreshPage为false。

// 是否开启页面刷新
let hasOpenRefreshPage = false;

// 将线程保存,方便强制退出
let refreshPageThread;

// 初始化页面刷新
initRefreshPage();

// 请求截图权限
requestScreenCapture();

// 开启页面刷新
hasOpenRefreshPage = true;
// 循环截图,验证页面刷新是否有效
for (let index = 0; index < 10; index++) {
    images.captureScreen();
    console.log(index);
}

// 关闭页面刷新
hasOpenRefreshPage = false;

// 停止页面刷新线程
refreshPageThread.interrupt();
// 置空,如果再开启线程时,作用大
refreshPageThread = null;

// 初始化页面刷新
function initRefreshPage() {
    refreshPageThread = threads.start(function () {
        // 如果开启刷新页面,一直循环
        while (true) {
            // 如果开启了页面刷新
            if (hasOpenRefreshPage) {
                toast(".");
            }
            sleep(2000);
        }
    });
}

4.操作函数

1.概况

大家需要注意,除了通过captureScreen函数获取图片类型的数据外,其他图片数据都需要手动回收,否则会产生内存积累,最终导致内存溢出。我推荐大家使用通过captureScreen函数获取图片数据,这种方式获取的图片,脚本会自动回收,大家不用担心内存溢出问题。

2.read

读取本地图片,需要传递图片绝对路径一个参数,参数类型为字符串,返回图片数据,返回类型为图片。

let fileRootPath = "/storage/emulated/0/com.py.test/";
let img = images.read(fileRootPath + "test.png");
console.log(img);

// 回收图片
img.recycle();	

2.load

读取网络图片,需要传递图片链接一个参数,参数类型为字符串,返回图片数据,返回类型为图片。

let img = images.load("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png");
console.log(img);

// 回收图片
img.recycle();

3.copy

复制一份图片数据,需要传递图片链接一个参数,参数类型为字符串,返回图片数据,返回类型为图片。

let img = images.load("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png");
let copyImg = images.copy(img);
console.log(copyImg);

// 回收图片
copyImg.recycle();
img.recycle();

4.save

将图片数据保存到本地,需要传递四个参数,分别为图片数据、本地绝对路径、图片格式(可以填写png、jpeg、jpg或webp,默认为png)和图片质量(范围0~100,默认为100),参数类型分别为图片、字符串、字符串和数字。如果文件存在时会被覆盖,如果图片不存在时会被新建。图片绝对路径中的文件类型最好和图片格式一致,图片质量0代表最模糊,而100代表最清晰。

let img = images.load("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png");
let fileRootPath = "/storage/emulated/0/com.py.test/";
let fileSavePath = fileRootPath + "baidu_test.png";
images.save(img, fileSavePath, "png", 100);

// 回收图片
img.recycle();

5.fromBase64与toBase64

fromBase64函数用于base64数据转换为图片,需要传递base64数据一个参数,参数类型为字符串,返回图片数据,返回类型为图片。

toBase64函数用于图片数据转换为base64数据,需要传递三个参数,分别为图片数据、图片格式(可以填写png、jpeg、jpg或webp,默认为png)和图片质量(范围0~100,默认为100),参数类型分别为图片、字符串和数字,返回base64数据,返回类型为字符串。图片质量0代表最模糊,而100代表最清晰。

let img = images.load("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png");
// 图片转化为base64
let base64String = images.toBase64(img);

// base64转换为图片
let base64Img = images.fromBase64(base64String);
console.log(base64Img);

// 回收图片
base64Img.recycle();
img.recycle();

6.fromBytes与toBytes

fromBytes函数用于byte数据转换为图片,需要传递byte数据一个参数,参数类型为数组,返回图片数据,返回类型为图片。

toBytes函数用于图片数据转换为byte数据,需要传递三个参数,分别为图片数据、图片格式(可以填写png、jpeg、jpg或webp,默认为png)和图片质量(范围0~100,默认为100),参数类型分别为图片、字符串和数字,返回byte数据,返回类型为数组。图片质量0代表最模糊,而100代表最清晰。

let img = images.load("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png");
// 图片转化为byte数组
let byteArray= images.toBytes(img);

// byte数组转换为图片
let byteImg = images.fromBytes(byteArray);
console.log(byteImg);

// 回收图片
byteImg.recycle();
img.recycle();

7.readPixels

获取图片的像素数据和宽高,需要传递本地绝对路径一个参数,参数类型为字符串,返回图片信息,返回类型为对象。其中,返回对象中属性data、width和height分别代表图片具体信息、图片像素宽度和图片像素高度。使用这个函数,主要是获取图片的像素宽度和像素高度,不要尝试使用或者打印data属性的数据,具体原因请看注意事项。

let fileRootPath = "/storage/emulated/0/com.py.test/";
let fileSavePath = fileRootPath + "baidu_test.png";
let imageObj = images.readPixels(fileSavePath);
let imageKey = Object.keys(imageObj);
console.log("imageObj属性:" + imageKey);
let currentWidth = imageObj.width;
let currentHeight = imageObj.height;
let currentData = imageObj.data;
console.log("宽度:" + currentWidth + ",高度:" + currentHeight);
console.log(typeof currentData);
let dataKey = Object.keys(currentData);
console.log("data属性:" + dataKey.length);

注意:

其实autojs的控制台输出是有个bug,如果输出内容非常多,会导致已连接设备断开。通过上面打印可以看出,data中的属性就接近14万,然后每个属性里面又包含具体信息,那个数据量是非常恐怖的。一般情况下,我们是很难触发这个bug的,输出这么多数据会导致断开连接,我感觉这根本不是bug,而是对避免设备接收海量数据的一种保护。这个情况很少出现,如果出现自动断开设备,并且自己有打印某个数据的代码,可以考虑这个原因。其实,如果是输出海量数据导致断开设备,很好判断,代码编辑器的控制台输出会被清空。

8.clip

截取图片,需要传递五个参数,分别为图片、截取区域左上角横坐标、截取区域左上角纵坐标、截取宽度和截图高度,参数类型分别为图片、数字、数字、数字和数字,返回截取的图片,返回类型为图片。

let img = images.load("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png");

// 截取图片
let clipImg = images.clip(img, 10, 10, 100, 200);

// 获取截取图片的宽度和高度
console.log("图片宽度:" + clipImg.width + ",图片高度:" + clipImg.height);

// 回收图片
clipImg.recycle();
img.recycle();

9.resize

调整图片大小,需要传递三个参数,分别为需要调整的图片、调整大小和插值方法(可以不传递),参数类型分别为图片、数组(第一值为宽度,第二个值为高度)和字符串,返回调整后的图片,返回类型为图片。插值方法是可以不传递的,默认是LINEAR(线性插值),其他可填写的值可以参考官方文档。插值方法我都知道干什么用的,我一般不传递这个参数,如果有需要,可自行研究。打印信息中的“opencv”是因为有些图片处理函数用到了,我这里又第一次使用,因此会有个初始化的信息。在脚本开发中,初次使用“opencv”相关函数时,也会打印。如果你再运行一次脚本,就会发现关于“opencv”初始化的打印信息已经没有了。

let img = images.load("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png");

// 获取原图片大小
console.log("原图片,宽度:" + img.width + ",高度:" + img.height);

// 调整数组[宽度, 高度]
let resizeArray = [100, 200]
// 调整图片大小
let resizeImg = images.resize(img, resizeArray);
// 获取调整后图片大小
console.log("调整后图片,宽度:" + resizeImg.width + ",高度:" + resizeImg.height);

// 回收图片
resizeImg.recycle();
img.recycle();

10.scale

缩放图片,需要传递四个参数,分别为图片、宽度倍数、高度倍数和插值方法(可以不传递),参数类型分别为图片、数字、数字和字符串,返回缩放后的图片,返回类型为图片。其中,插值方法和resize函数的插值方法完全一致,一般不用传递。最后生成的图片大小等于原宽度或高度乘以对应的宽度倍数或高度倍数,也就是说可以放大或者缩小图片。

let img = images.load("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png");

// 获取原图片大小
console.log("原图片,宽度:" + img.width + ",高度:" + img.height);


// 缩小图片
let scaleImg = images.scale(img, 0.5, 0.5);
// 获取缩小后图片大小
console.log("缩小后图片,宽度:" + scaleImg.width + ",高度:" + scaleImg.height);

// 回收图片
scaleImg.recycle();
img.recycle();

11.rotate

将图片逆时针旋转,需要传递四个参数,分别为图片、旋转角度、旋转中心横坐标(默认为图片中心点,可以不传递)和旋转中心纵坐标(默认为图片中心点,可以不传递),参数类型分别为图片、数字、数字和数字,返回旋转后的图片,返回类型为图片。

let img = images.load("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png");

// 获取原图片大小
console.log("原图片,宽度:" + img.width + ",高度:" + img.height);


// 逆时针旋转图片
let rotateImg = images.rotate(img, 90);
// 获取旋转后图片大小
console.log("旋转后图片,宽度:" + rotateImg.width + ",高度:" + rotateImg.height);

// 回收图片
rotateImg.recycle();
img.recycle();

12.concat

拼接两张图片,需要传递三个参数,分别为图片1、图片2和拼接位置(可以传递LEFT、RIGHT、TOP和BOTTOM,分别表示左、右、上和下,默认为RIGHT,可以不传递),参数类型分别为图片、图片和字符串,返回拼接后的图片,返回类型为图片。拼接方式为将图片2拼接到图片1的某个位置,具体位置由第三个参数拼接位置决定。其实左右拼接时,我们通过切换传递参数图片1和图片2的位置就能实现,只有上下拼接时需要传递第三个参数拼接位置。

let img = images.load("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png");

// 获取原图片大小
console.log("原图片,宽度:" + img.width + ",高度:" + img.height);


// 拼接图片
let concatImg = images.concat(img, img);
// 拼接后图片大小
console.log("拼接图片,宽度:" + concatImg.width + ",高度:" + concatImg.height);

// 回收图片
concatImg.recycle();
img.recycle();

13.grayscale

灰度化图片,需要传递需要操作的图片一个参数,返回灰度后的图片,返回类型为图片。

let img = images.load("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png");

// 获取灰度后的图片
let grayscaleImg = images.grayscale(img);
// 本地文件路径
let fileRootPath = "/storage/emulated/0/com.py.test/";
let fileSavePath = fileRootPath + "baidu_grayscale.png";
// 保存到本地
images.save(grayscaleImg, fileSavePath);

// 回收图片
grayscaleImg.recycle();
img.recycle();

14.threshold

将图片阈值化,需要传递四个参数,分别为图片、阈值、最大值和阈值化类型(默认为BINARY,可以不传递)。这个函数作用是将超过阈值的值设置为最大值,阈值化类型只能填写几个值,具体可以查看官方文档。可以通过阈值化函数完成二值化功能,具体可以查看下面代码。在图像比对过程中,如果有些颜色会干扰比对准确性,二值化是个不错的选择,能够降低颜色干扰,将二值化的图片加载到按键助手中然后再进行选点。二值化最关键的就是阈值的选择,如果设置过小会导致处理的颜色过多,如果设置的过大会导致干扰颜色没有去掉,需要不断调整来达到最优。但是,我平时很少使用二值化,因为我选比对位置时就尽量避免了干扰。

let img = images.load("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png");

// 获取二值化后的图片
let thresholdImg = images.threshold(img, 100, 255);
// 本地文件路径
let fileRootPath = "/storage/emulated/0/com.py.test/";
let fileSavePath = fileRootPath + "baidu_threshold.png";
// 保存到本地
images.save(thresholdImg, fileSavePath);

// 回收图片
thresholdImg.recycle();
img.recycle();

15.adaptiveThreshold

对图片进行自适应阈值化处理,需要传递的参数很多,我根本没用过这个函数,哪怕使用二值化功能,我觉得使用上面threshold函数足够。并且,官方文档对这个函数的介绍也很少,如果感兴趣,可以根据官方文档给出的链接自行学习。

16.cvtColor、inRange、interval、blur、medianBlur、gaussianBlur、getSimilarity与matToImage

这些函数很少,就不花大量时间去学习,然后再介绍了。大家如果感兴趣,可以根据官方文档自行学习。

5.颜色函数

1.概况

再次提醒,此部分内容涉及的点信息,都是依托和无障碍操作一样的坐标信息,横屏状态下通过按键助手获取到的信息需要通过上面介绍的辅助工具进行转换。

2.colors.toString

这个函数应该属于颜色模块,由于颜色模块常用函数比较少,就没有单独开一个文章。大家要知道,这个函数不属于图片处理模块,需要通过“colors.”调用。这个函数作用就是颜色ARGB值转换为十六进制颜色字符串,常配合下面的pixel函数使用,这里就不介绍了。

2.pixel

获取图片某个点的颜色RGB值,需要传递三个参数,分别为图片、点横坐标和点纵坐标,参数类型分别为图片、数字和数字,返回颜色RGB值,返回类型为数字。由于ARGB值超出js数字类型存储最大值,直接输出RGB值为负数。

// 获取截图权限
requestScreenCapture();

// 截图
let img = images.captureScreen();

// 获取点(280, 450)颜色RGB值
let colorNumber = images.pixel(img, 280, 450);
console.log(colorNumber);

let colorString = colors.toString(colorNumber);
console.log(colorString);

3.findColor

在图片中查找某个颜色点信息,需要传递三个参数,分别为图片、颜色值和查找配置(可不传递),参数类型分别为图片、数字或字符串和对象,返回点坐标信息,返回类型为对象(属性x代表横坐标值,属性y代表纵坐标值)。第二个参数颜色值可以为pixel函数获取到的RGB值,也可以是通过colors.toString函数将RGB值转换为RGB字符串。其中,通过colors.toString函数转换的RGB字符串为9位,实际上autojs使用到的RGB字符串为7位,需要将字符串第2和第3位的“f”字符去掉。

第三个参数查找配置,参数类型为对象,里面有两个属性,分别为region和threshold。当对象中不设置region属性时,代表查找整张图片。当赋值region属性时,需要赋值数组,数组接收数据类型为数字,数组长度可以为2或者4。当数组长度为2时,数组第一个位置为查找区域左上角横坐标,第二个位子为查找区域左上角纵坐标,代表查找区域为从数组约定的左上角到图片的右下角;当数组长度为4时,数组第一和第二位置的作用和数组长度为2时完全一致,第三和第四个位置分别代表查找区域右下角横坐标和纵坐标,代表查找区域完全按照数组约定范围进行查找。threshold属性需要赋值数字,数字要求为0~255(越小越相似,0为颜色相等,255为任何颜色都能匹配),默认值为4。

此函数找到第一个点匹配的点就会返回,如果没有找到任何与颜色匹配的点会返回null,使用返回对象之前需要先判断是否为空。函数进行了全局保存,可以通过“images.”调用可以,也可以直接调用,我更推荐“images.”这种调用方式。

// 获取截图权限
requestScreenCapture();

// 截图
let img = images.captureScreen();
// 查找点信息
let point = images.findColor(img, "#0077d9");

// 如果查找到点信息
if (point) {
    console.log("找到颜色,横坐标为" + point.x + ",纵坐标为" + point.y);
}

4.findColorInRegion

在图片某个区域查找某个颜色的点信息,需要传递七个参数,分别为图片、颜色值、查找区域左上角横坐标(从这个位置开始,可不传递)、查找区域左上角纵坐标、查找区域宽度、查找区域高度和颜色相似度,参数类型分别为图片、字符串、数字、数字、数字、数字和数字。这个函数和findColor函数功能类似,实际上将findColor函数中第三个参数查找配置当做参数传递了。

findColor函数和findColorInRegion函数前两个参数功能作用完全一致,只是查找区域限制方式有所区别。findColor函数采用左上角和右下角两个点的横纵坐标来确定区域,而findColorInRegion函数采用左上角横纵坐标加宽度和高度的方式来确定区域。在autojs中,左上角横纵坐标加宽度和高度这种方式来确定区域最为常用。findColorInRegion函数最后一个参数颜色相似度对应findColor函数查找配置参数的threshold属性,两者用法与作用完全一致。

函数进行了全局保存,可以通过“images.”调用可以,也可以直接调用,我更推荐“images.”这种调用方式。

通过上面分析可以看出,findColor和findColorInRegion函数功能类似,只是传值方式不同罢了。我们一般情况下,会使用其中一个函数即可。说实话,这个种单颜色查找的方式很容易出错,除非我们确定某个区域,然后保证没有别的颜色干扰时才会使用。

// 获取截图权限
requestScreenCapture();

// 截图
let img = images.captureScreen();
// 查找点信息
let point = images.findColorInRegion(img, "#0077d9");

// 如果查找到点信息
if (point) {
    console.log("找到颜色,横坐标为" + point.x + ",纵坐标为" + point.y);
}

5.findAllPointsForColor

在图片中查找某个颜色所有点信息,参数与findColor函数完全一致,返回所有点信息,返回类型为数组,如果没有找到任何与颜色匹配的点会返回null。参数用法和要求参考findColor函数,与findColor函数唯一不同是,这个函数查找所有点信息,而findColor函数只查找一个点信息。

// 获取截图权限
requestScreenCapture();

// 截图
let img = images.captureScreen();
// 查找点信息
let points = images.findAllPointsForColor(img, "#0077d9");

// 如果查找到点信息
if (points) {
    points.forEach((point, index) => {
        console.log("找到匹配颜色第" + (index + 1) + "个点,横坐标为" + point.x + ",纵坐标为" + point.y);
    });
}

6.findColorEquals

在图片某个区域与颜色完全相同的某个点,需要传递六个参数,与findColorInRegion函数前六个参数完全一致。这个函数更像是将findColorInRegion函数第七个参数值固定为0了,处理参数少传递一个外,其他完全一致。这个函数功能,完全可以通过findColorInRegion函数配置来完成。函数进行了全局保存,可以通过“images.”调用可以,也可以直接调用,我更推荐“images.”这种调用方式。

// 获取截图权限
requestScreenCapture();

// 截图
let img = images.captureScreen();
// 查找点信息
let point = images.findColorEquals(img, "#0077d9");

// 如果查找到点信息
if (point) {
    console.log("找到颜色,横坐标为" + point.x + ",纵坐标为" + point.y);
}

7.findMultiColors

多点找色,需要传递四个参数,分别为图片、第一点的颜色、相对点坐标颜色信息和查找配置,参数类型分别为图片、字符串、数组和对象,返回点信息,返回类型为对象。这个函数的原理是先找到匹配第一个颜色的点,然后用相对点坐标颜色数组来比对这个点周围的颜色,如果都比对成功就返回第一个颜色点信息。相对点坐标颜色数组是一个二维数组(数组里面包含数组),内部数组一共为三个值,分别为点相对横坐标(当前点横坐标-第一个点横坐标)、点相对纵坐标(当前点纵坐标-第一个点纵坐标)和点颜色字符串,类型分别为数字、数字和字符串。这个函数的查找配置参数和findColor函数的查找配置参数完全一致,具体可以查看findColor函数部分。

但是这个函数实际使用过程中并不好用,有以下几个原因:第一,参数查找配置类型为对象,设置时不方便;第二,返回数据为点对象,使用不方便;第三,无法按照个人需求进行特殊设置。我建议大家使用,我封装的这个findMultiColorsBySimilar函数来代替findMultiColors函数功能,当然,如果大家习惯使用findMultiColors函数也可以。findMultiColorsBySimilar函数各个参数和返回值的介绍,代码中已经介绍非常详细了。

// 获取截图权限
requestScreenCapture();

// 截图
let img = images.captureScreen();

// 以辅助工具勾选“多点找色”功能转换后的数据为例
let pointArray = findMultiColorsBySimilar(img, "#222222", [[-34, 14, "#f5c51e"], [-27, -8, "#f5c51e"]], 20);

// 判断是否找到点
if (pointArray) {
    console.log("匹配点横坐标为" + pointArray[0] + ",纵坐标为" + pointArray[1]);
}else {
    console.log("没有找到匹配点");
}

// 匹配所有颜色
// img:图片
// firstColor:第一颜色值
// colorsArray:颜色数组
// threshold:阈值,严格匹配推荐:10-20,常规匹配:推荐:20-30,宽松匹配推荐:30-40
// region:识别范围数组,分别填写左上角横坐标,左上角纵坐标,宽度和高度
// return 找到点时,返回[点横坐标, 点纵坐标]数组;否则,返回undefined
function findMultiColorsBySimilar(img, firstColor, colorsArray, threshold, region) {
    let currentObj = {};
    currentObj["threshold"] = threshold;
    if (region) {
        currentObj["region"] = region;
    }

    let currentPoint = images.findMultiColors(img, firstColor, colorsArray, currentObj);
    if (currentPoint) {
        currentPoint = currentPoint.toString();
        currentPoint = currentPoint.substring(1, currentPoint.length - 1);
        currentPoint = currentPoint.replace(/\s/g, "");
        let currentPointArray = currentPoint.split(",");
        currentPointArray[0] = Number(currentPointArray[0]);
        currentPointArray[1] = Number(currentPointArray[1]);
        return currentPointArray;
    }
    return undefined;
}

8.detectsColor

判断图片某个点是否成功匹配特定颜色,需要传递六个参数,分别为图片、匹配颜色、点横坐标、点纵坐标、颜色相似度(默认为16,填写范围为0~255,可以不传递)和匹配算法(默认是diff,可以不传递),参数类型分别为图片、字符串、数字、数字、数字和字符串,返回是否匹配成功,返回类型为boolean。其中,参数匹配算法只能传递五个固定值,具体情况可以查看官方文档。一般情况下,我们不需要传递最后两个参数,使用默认值即可。

// 获取截图权限
requestScreenCapture();

// 截图
let img = images.captureScreen();

// 获取点颜色是否匹配
let hasFind = images.detectsColor(img, "#f4c51f", 105, 258);

// 如果匹配到点颜色
if (hasFind) {
    console.log("点颜色成功匹配");
}else {
    console.log("未匹配点颜色");
}

9.detectsMultiColors

多点比色,图像处理用的最多的函数。但是,非常遗憾这个函数免费版根本不存在,为了防止大家认为我判断方式不正确,我先验证了已经存在的函数,然后再验证此函数是否存在。

// 确定存在的函数
console.log(Object.hasOwnProperty.call(images, "detectsColor"));

// 判断多点比色函数是否存在
console.log("多点比色函数是否存在:" + Object.hasOwnProperty.call(images, "detectsMultiColors"));

这么重要的函数竟然不存在,是不是这个脚本不能用了?没关系,我给大家直接封装了个类似功能的函数,性能方面大家也不用担心,我已经实际使用过了。大家以后可以使用matchPointColorBySimilar函数来完成多点比色功能,代码对于参数介绍已经非常详细了。另外的辅助函数,大家可以忽略,如果感兴趣可以自行学习。其实,这几个辅助函数用的比较多,这种自己封装函数附带几个配套辅助函数的方式容易出现混乱。大家不用担心,我后期会做一个类似插件的代码,会介绍各种封装函数,大家可以直接调用插件里面的函数,对于插件里面的内容可以不用关心。当然,这个插件肯定需要付费获取的,到时候我会把所有能提供的附加资源都放一个地方,打赏一次就可以获取所有内容。我会和开始说的那样,整个autojs部分,尽量让大家花费不超过10元就能获取全部内容。希望小伙伴们能理解,毕竟博主也需要吃饭的。我一直都是这个定价,为了让我有点收入,也不让小伙伴们破费过多,应该算是个双赢。我在基础阶段介绍的所有自己封装函数和插件中目前函数功能完全相同的,不存在插件相同函数功能更加强大的情况。但是,可能存在插件函数升级的情况,一般不会再升级基础部分的函数。

// 获取截图权限
requestScreenCapture();

// 截图
let img = images.captureScreen();

let hasFind = matchPointColorBySimilar(img, [[75, 27, "#222222"], [41, 41, "#f5c51e"], [48, 19, "#f5c51e"]], 20);
console.log(hasFind);



// 通过相似度匹配各个点颜色
// img: 图片对象
// pointColors:二位数组,最里面的数据依次是x,y,color
// threshold:阈值,严格匹配推荐:10-20,常规匹配:推荐:20-30,宽松匹配推荐:30-40
// hasLog:是否打印,用于测试
function matchPointColorBySimilar(img, pointColors, threshold, hasLog) {
    for (let index = 0; index < pointColors.length; index++) {
        let pointColor = pointColors[index];
        let currentX = pointColor[0];
        let currentY = pointColor[1];
        let currentColor = pointColor[2];
        let pointColorString = getPointColor(img, currentX, currentY);
        if (hasLog) {
            console.log("点" + (index + 1));
            console.log("X:" + currentX + ",Y:" + currentY);
        }
        if (!isColorSimilar(currentColor, pointColorString, threshold, hasLog)) {
            return false;
        }

    }
    return true;
}

// 获取点颜色
function getPointColor(img, currentX, currentY) {
    return colors.toString(images.pixel(img, currentX, currentY)).replace("#ff", "#");
}

// 计算颜色相似度(欧几里得距离算法)
function isColorSimilar(color1, color2, threshold, hasLog) {
    let rgb1 = typeof color1 === 'string' ? hexToRgb(color1) : color1;
    let rgb2 = typeof color2 === 'string' ? hexToRgb(color2) : color2;
    // 计算三维空间距离
    let deltaR = rgb1[0] - rgb2[0];
    let deltaG = rgb1[1] - rgb2[1];
    let deltaB = rgb1[2] - rgb2[2];
    let distance = Math.sqrt(deltaR * deltaR + deltaG * deltaG + deltaB * deltaB);
    if (hasLog) {
        console.log(color1);
        console.log(color2);

        console.log(distance);
    }


    return distance <= threshold;
}

// 十六进制转RGB数组(兼容带#号的格式)
function hexToRgb(hex) {
    hex = hex.replace(/^#/, '');
    if (hex.length === 3) { // 处理简写格式如#FFF
        hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
    }
    var num = parseInt(hex, 16);
    return [num >> 16 & 255, num >> 8 & 255, num & 255];
}

10.findImage与findImageInRegion

findImage和findImageInRegion均是在大图片中寻找小图片,这两个函数功能非常类似findColor和findColorInRegion函数,只是找色功能变成了找图功能。

findImage函数需要传递三个参数,分别为大图片、小图片和查找配置(可以不传递),参数类型分别为图片、图片和对象,返回是否找到,返回类型为boolean。对于查找配置参数,一共有三个属性,分别为threshold、region和level。threshold代表图片相似度,需要赋值数字,默认是0.9,范围为0~1。region代表查找区域,功能和findColor函数查找配置中的region属性功能完全一致,可以去findColor函数位置查看相关介绍。level属性一般不用填写,如果需要可以查看官方文档相关介绍。

findImageInRegion函数需要传递七个参数,分别为大图片、小图片、查找区域左上角横坐标(从这个位置开始,可不传递)、查找区域左上角纵坐标、查找区域宽度、查找区域高度和图片相似度,参数类型分别为图片、图片、数字、数字、数字、数字和数字,返回是否找到,返回类型为boolean。此函数与findColorInRegion函数非常相似,将findColorInRegion函数的第二个参数颜色值改为了小图片,最后一个参数改为图片相似度(默认是0.9,范围为0~1)。

两个函数均进行了全局保存,可以通过“images.”调用可以,也可以直接调用,我更推荐“images.”这种调用方式。

我非常不推荐找图这种方式,以模拟器为例,相同模拟器不同设备之间颜色都会有色差,兼容性比价差。而且,一个脚本需要大量图像处理,需要将大量小图片保存到脚本中,增加脚本占用空间。再者,找图识别速度远低于找色速度,找色选点一般不会超过10个,一张图片的点都是几百上千的。

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "图片处理";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
let relativeFolder = devRelativeFolder;

// 当前保存图片路径
let currentImageFolder = relativeFolder + "images/";

// 当前文件路径
let currentFilePath = files.path(currentImageFolder + "findImage.png");

// 获取截图权限
requestScreenCapture();

// 如果文件存在
if (files.exists(currentFilePath)) {
    let tempImg = images.read(currentFilePath);
    // 截图
    let img = images.captureScreen();
    let point = images.findImage(img, tempImg);

    // 如果找到图片
    if (point) {
        console.log("findImage函数找到坐标,横坐标为" + point.x + ",纵坐标为" + point.y);
    } else {
        console.log("findImage函数没有找到坐标");
    }

    point = images.findImageInRegion(img, tempImg);

    // 如果找到图片
    if (point) {
        console.log("findImageInRegion函数找到坐标,横坐标为" + point.x + ",纵坐标为" + point.y);
    } else {
        console.log("findImageInRegion函数没有找到坐标");
    }

    // 回收加载的模板
    tempImg.recycle();

} else {
    console.log("文件不存在");
}

11.matchTemplate

这个函数虽然也是在大图片查找小图片,但是可以查找多个匹配的图片。这个函数可以传递的参数非常多,官方文档介绍的已经很详细,为了防止这个函数不存在,我也简单测试下功能。

// 生产环境相对路径文件夹
let procRelativeFolder = "./";

// 项目名称,根据实际情况替换
let projectName = "图片处理";
// 开发环境相对路径文件夹
let devRelativeFolder = files.cwd() + "/" + projectName + "/";

// 相对路径文件夹
// 开发时,切换到开发环境
// 打包时,切换到生产环境
// 读取相对路径文件时,都采用这种方式
let relativeFolder = devRelativeFolder;

// 当前保存图片路径
let currentImageFolder = relativeFolder + "images/";

// 当前文件路径
let currentFilePath = files.path(currentImageFolder + "findImage.png");

// 获取截图权限
requestScreenCapture();

// 如果文件存在
if (files.exists(currentFilePath)) {
    let tempImg = images.read(currentFilePath);
    // 截图
    let img = images.captureScreen();
    let MatchingResult = images.matchTemplate(img, tempImg);

    // 如果找到图片
    if (MatchingResult) {
        let points = MatchingResult.points;
        points.forEach((point, index) => {
            console.log("第" + (index + 1) + "点,横坐标为"+ point.x + ",纵坐标为" + point.y);
        });
    } else {
        console.log("matchTemplate函数没有找到坐标");
    }

    // 回收加载的模板
    tempImg.recycle();

} else {
    console.log("文件不存在");
}

12.findCircles

用于查找图片中的圆行,由于我平时不怎么用这个函数,并且官方文档中已经介绍的非常详细,具体内容请参考官方文档。为了检查这个函数是否存在,我做了一个简单使用。需要注意的是如果没找到圆会返回空数组,而不是null。

// 获取截图权限
requestScreenCapture();

// 截图
let img = images.captureScreen();

// 灰度化图片
let gray = images.grayscale(img);
// 找圆
let arr = images.findCircles(gray, {
    dp: 1,
    minDst: 80,
    param1: 100,
    param2: 100,
    minRadius: 50,
    maxRadius: 80,
});
if (arr.length) {
    console.log(arr);
    arr.forEach((point, index) => {
        console.log("第" + (index + 1) + "点,横坐标为" + point.x + ",纵坐标为" + point.y);
    });
} else {
    console.log(arr);
    console.log("没找到圆");
}


// 回收图片
gray.recycle();

13.detectAndComputeFeatures与matchFeatures

我大体看了下,感觉就是一个匹配图片的新方式,感感觉上面大图片找小图片的函数功能类似,只是查找图片的方式不同罢了。通过detectAndComputeFeatures获取小图片和大图片的特征值,然后传到matchFeatures函数进行匹配就行了,特征值具体数据我们可以不考虑。

比较遗憾的是,这两个函数在免费版中不存在,我们不用考虑如何使用这两个函数了。

// 确定存在的函数
console.log(Object.hasOwnProperty.call(images, "detectsColor"));

// 判断计算特征值函数是否存在
console.log("计算特征值函数是否存在:" + Object.hasOwnProperty.call(images, "detectAndComputeFeatures"));

// 判断特征值比对函数是否存在
console.log("特征值比对函数是否存在:" + Object.hasOwnProperty.call(images, "matchFeatures"));

6.对象属性

1.image

对应类型为图片的参数或者返回值,通过下面函数打印一下图片对象的常用属性。

// 获取截图权限
requestScreenCapture();

// 截图
let img = images.captureScreen();
console.log(img.width);
console.log(img.height);

console.log("获取图片对象函数");
getJavaObjectFunctions(img);
console.log("---------------------------------------");

console.log("获取图片对象属性");
getJavaObjectProperties(img);
console.log("---------------------------------------");

// 获取Java对象的所有函数
function getJavaObjectFunctions(javaObject) {
    // 1. 获取对象的类 (jclass)
    let jclass = javaObject.getClass();

    // 2. 获取类中声明的所有方法
    let methods = jclass.getDeclaredMethods();

    // 3. 遍历并打印方法名
    for (let i = 0; i < methods.length; i++) {
        // 使用方法名 getName 和参数类型 getParameterTypes 来区分重载方法
        let method = methods[i];
        let methodName = method.getName();
        let parameterTypes = method.getParameterTypes();
        // 将参数类型数组转换为可读字符串
        let params = [];
        for (let j = 0; j < parameterTypes.length; j++) {
            params.push(parameterTypes[j].getSimpleName());
        }
        console.log("函数名: " + methodName + ", 参数类型: (" + params.join(", ") + ")");
    }
}

// 获取Java对象的所有属性
function getJavaObjectProperties(javaObject) {
    // 1. 获取对象的类 (jclass)
    let jclass = javaObject.getClass();

    // 2. 获取类中声明的所有字段(属性)
    let fields = jclass.getDeclaredFields();

    // 3. 遍历并打印字段信息
    let properties = [];
    for (let i = 0; i < fields.length; i++) {
        let field = fields[i];
        let fieldName = field.getName();
        let fieldType = field.getType().getSimpleName();
        let modifier = field.getModifiers();
        
        // 将修饰符转换为可读字符串
        let modifierStr = java.lang.reflect.Modifier.toString(modifier);
        
        console.log("属性名: " + fieldName + 
                   ", 类型: " + fieldType + 
                   ", 修饰符: " + modifierStr);
        
        properties.push({
            name: fieldName,
            type: fieldType,
            modifier: modifierStr
        });
    }
    
    return properties;
}

通过打印信息发现,图片对象有很多函数,也有几个属性,属性都是私有属性,无法直接调用。但是js中封装了width和height两个简单属性,可以直接获取图片的宽度和高度。我并不知道还有没有其他可以直接调用的属性,我还是推荐除了宽度和高度两个属性可以直接过去外,其他值最好通过函数的方式来获取。

下面是各个函数的使用方式,没有涉及的函数代表无法使用或者使用报错,大家最好只使用下面介绍函数,具体每个函数的功能都有注释说明。我一般很少使用图片对象自己的函数,通常使用图片处理(images对象)下面的函数。

// 获取截图权限
requestScreenCapture();

// 截图
let img = images.captureScreen();

// 获取图片宽度
console.log("图片宽度:" + img.getWidth());
// 获取屏幕高度
console.log("图片高度:" + img.getHeight());

// 获取图片的RGB颜色值
let colorNumber = img.pixel(115, 447);
// 将RGB颜色值转换为字符串
let colorString = colors.toString(colorNumber);
console.log("颜色字符串:" + colorString);

// 将图片保存到本地
let fileRootPath = "/storage/emulated/0/com.py.test/";
let currentFilePath = fileRootPath + "image_save_to.png";
img.saveTo(currentFilePath);

// 获取图片Android Bitmap对象
let imageGetBitmap = img.getBitmap();
console.log("get Android Bitmap对象:" + imageGetBitmap);

// 创建一个图片副本
let imgCopy = img.clone();
console.log("副本图片:" + imgCopy);

// 将Android Bitmap对象转化为图片
let bitmapImage = img.ofBitmap(imageGetBitmap);
console.log("Bitmap转换后的图片:" + bitmapImage);


// 获取图片OpenCV Mat对象
let imageMat = img.getMat();
console.log("图片 Mat对象:" + imageMat);

// 将OpenCV Mat对象转化为图片
let matImage = img.ofMat(imageMat);
console.log("OpenCV Mat转换后的图片:" + matImage);


// 回收图片(img由于是直接截图,不用手动回收),但是其他情况产生的图片需要回收
imgCopy.recycle();
bitmapImage.recycle();
matImage.recycle();

2.point

对应类型为点对象的参数或者返回值,点对象只有x和y两个属性,分别代表横坐标和纵坐标。前面有大量函数已经使用,比如多点找色函数、找图函数等,都会返回点对象。

3.matchingResult

这个应该是matchTemplate函数特有返回值,一般只知道属性points会返回匹配的点数组即可。官方文档对于这个对象介绍非常详细,我一般不会使用matchTemplate函数函数,在这里不做过多介绍了。

7.总结

特别注意,只有通过个人主页博客或者个人介绍中方式,才能获取源码