Java通过ADB免存截图获取指定坐标RGB颜色及红色识别异常问题
Java结合ADB获取指定坐标RGB时红色系颜色识别异常的解决方法
我通过Java结合ADB实现游戏自动化,编写了无需保存截图到本地即可获取指定(x,y)坐标RGB值的方法(保存截图耗时约3秒,效率太低)。但在YouTube的RGB测试视频中测试时,红色及红色系颜色出现识别异常,其他颜色显示正常,无法输出目标坐标的准确结果。现有代码如下:
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class RGBColorAtCoordinates { public static void main(String[] args) { int x = 1200; // Replace with your desired x-coordinate int y = 500; // Replace with your desired y-coordinate try { // ADB command to get pixel color at (x, y) String adbCommand = String.format("adb shell screencap -p | sed 's/\\r$//' | head -c %d | tail -c %d", (x + 1) * 4, 4); // Execute adb command Process process = Runtime.getRuntime().exec(adbCommand); // Read output BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); String line; StringBuilder output = new StringBuilder(); while ((line = reader.readLine()) != null) { output.append(line); } reader.close(); // Parse RGB values from output (example, you may need to adjust based on actual output format) String colorOutput = output.toString(); if (colorOutput.length() == 4) { int red = (colorOutput.charAt(0) & 0xff); int green = (colorOutput.charAt(1) & 0xff); int blue = (colorOutput.charAt(2) & 0xff); int alpha = (colorOutput.charAt(3) & 0xff); System.out.printf("RGB color at (%d, %d): (%d, %d, %d)\n", x, y, red, green, blue); } else { System.err.println("Failed to get color information."); } } catch (IOException e) { e.printStackTrace(); } } }
问题原因分析
- PNG像素字节顺序错误:
screencap -p输出的是PNG格式图片,PNG的像素存储顺序为BGRA(蓝-绿-红-透明度),而代码中错误地按RGBA顺序解析,红色系颜色的红分量高、蓝分量低,顺序颠倒后会直接导致识别结果错误,其他颜色因RGB数值接近可能表现正常。 - 二进制数据用文本流读取:
BufferedReader是用于读取文本的工具,处理PNG二进制字节时会触发编码转换,导致字节值失真,红色相关字节更容易出现解析错误。 - 像素位置计算错误:代码中用
(x + 1) * 4计算截取长度,完全忽略了PNG文件的头部和块结构,实际截取的是PNG文件头内容,根本不是目标坐标的像素数据。
修正方案
方案1:Java直接解析PNG输入流(推荐,不依赖设备额外工具)
直接通过InputStream读取ADB输出的二进制PNG数据,解析PNG结构找到像素数据区域,再定位目标坐标的字节:
import java.io.IOException; import java.io.InputStream; import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; public class RGBColorAtCoordinates { public static void main(String[] args) { int targetX = 1200; int targetY = 500; try { Process process = Runtime.getRuntime().exec("adb shell screencap -p"); InputStream inputStream = process.getInputStream(); // 跳过PNG文件标识头(固定8字节:89 50 4E 47 0D 0A 1A 0A) inputStream.skip(8); // 读取IHDR块,获取屏幕宽高 byte[] ihdrLenBytes = new byte[4]; inputStream.read(ihdrLenBytes); int ihdrSize = bytesToInt(ihdrLenBytes, true); byte[] ihdrData = new byte[ihdrSize]; inputStream.read(ihdrData); int screenWidth = bytesToInt(new byte[]{ihdrData[0], ihdrData[1], ihdrData[2], ihdrData[3]}, true); int screenHeight = bytesToInt(new byte[]{ihdrData[4], ihdrData[5], ihdrData[6], ihdrData[7]}, true); // 跳过IHDR块的CRC校验(4字节) inputStream.skip(4); // 定位到IDAT块(存储像素压缩数据的块) byte[] chunkLen = new byte[4]; byte[] chunkType = new byte[4]; while (true) { inputStream.read(chunkLen); inputStream.read(chunkType); int chunkSize = bytesToInt(chunkLen, true); if (new String(chunkType).equals("IDAT")) { break; } // 跳过非IDAT块的数据和CRC校验 inputStream.skip(chunkSize + 4); } // 解压IDAT块的像素数据 Inflater inflater = new Inflater(true); InflaterInputStream inflaterStream = new InflaterInputStream(inputStream, inflater); // 计算目标像素的偏移量:每行screenWidth*4字节,跳过targetY行,再跳过targetX个像素的字节 long skipOffset = (long) targetY * screenWidth * 4 + (long) targetX * 4; inflaterStream.skip(skipOffset); // 读取目标像素的BGRA字节 byte[] pixelBytes = new byte[4]; inflaterStream.read(pixelBytes); // 转换为RGB格式(BGRA -> RGB) int red = pixelBytes[2] & 0xFF; int green = pixelBytes[1] & 0xFF; int blue = pixelBytes[0] & 0xFF; // int alpha = pixelBytes[3] & 0xFF; // 如需透明度可保留 System.out.printf("RGB color at (%d, %d): (%d, %d, %d)\n", targetX, targetY, red, green, blue); // 关闭资源 inflaterStream.close(); inputStream.close(); process.waitFor(); } catch (IOException | InterruptedException e) { e.printStackTrace(); } } // 字节数组转int(bigEndian为true表示大端字节序,PNG采用大端) private static int bytesToInt(byte[] bytes, boolean bigEndian) { int result = 0; if (bigEndian) { for (byte b : bytes) { result = (result << 8) | (b & 0xFF); } } else { for (int i = bytes.length - 1; i >= 0; i--) { result = (result << 8) | (bytes[i] & 0xFF); } } return result; } }
方案2:依赖设备工具简化命令(需设备预装busybox/imagemagick)
如果你的设备已经安装了busybox和imagemagick,可以用以下ADB命令直接获取RGB值,Java只需解析输出即可:
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class RGBColorAtCoordinates { public static void main(String[] args) { int x = 1200; int y = 500; try { String adbCommand = String.format("adb shell \"screencap -p | convert - -format '%%[pixel:p(%d,%d)]' info:\"", x, y); Process process = Runtime.getRuntime().exec(adbCommand); BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); String colorStr = reader.readLine(); reader.close(); // 解析输出格式(如rgb(255,0,0)) if (colorStr != null && colorStr.startsWith("rgb(")) { String[] rgbParts = colorStr.substring(4, colorStr.length()-1).split(","); int red = Integer.parseInt(rgbParts[0].trim()); int green = Integer.parseInt(rgbParts[1].trim()); int blue = Integer.parseInt(rgbParts[2].trim()); System.out.printf("RGB color at (%d, %d): (%d, %d, %d)\n", x, y, red, green, blue); } else { System.err.println("Failed to parse color output."); } process.waitFor(); } catch (IOException | InterruptedException e) { e.printStackTrace(); } } }
关键修正点
- 修正PNG像素字节顺序为BGRA,转换为RGB时调换红、蓝分量
- 用二进制流
InputStream读取PNG数据,避免文本编码导致的字节失真 - 正确解析PNG文件结构,跳过文件头和非像素块,定位到真实像素数据区域
内容的提问来源于stack exchange,提问作者AomineDaici
相关产品推荐
相关产品推荐

