【Processing】椭圆眼珠鼠标跟随
Processing图像处理与椭圆眼睛交互程序完全指南
本教程将详细讲解Processing的安装方法、图像加载技术、高分辨率屏幕适配,以及创建交互式椭圆眼睛的完整实现,包含数学算法分析。
如何安装Processing
系统要求
Processing支持Windows、macOS和Linux三大操作系统,具体要求如下:
- Windows:Windows 8或更高版本
- macOS:10.14 Mojave或更高版本
- Linux:Ubuntu 16.04或更高版本(需要GLIBC 2.17以上)
下载与安装步骤
Windows系统安装
- 访问Processing官网:https://processing.org/download
- 点击"Windows 64-bit"下载安装包
- 运行下载的
.exe
文件,按照向导完成安装 - 安装完成后,桌面会出现Processing图标
macOS系统安装
- 从官网下载macOS版本(.dmg文件)
- 双击.dmg文件,将Processing拖拽到Applications文件夹
- 在Launchpad或Applications文件夹中找到并运行Processing
- 首次运行时可能需要右键选择"打开"来绕过安全限制
Linux系统安装
# 方法1:使用官方脚本安装
curl https://processing.org/download/install.sh | sh# 方法2:手动下载并解压
wget https://github.com/processing/processing/releases/latest/download/processing-4.3-linux-x64.tgz
tar -xzf processing-4.3-linux-x64.tgz
cd processing-4.3
./processing
验证安装
安装完成后,打开Processing,创建一个简单的测试程序:
void setup() {size(400, 300);background(255, 0, 0); // 红色背景println("Processing安装成功!");
}void draw() {fill(255);ellipse(mouseX, mouseY, 50, 50);
}
运行此程序,如果看到红色背景和跟随鼠标的白色圆圈,说明安装成功。
第一部分:图像加载与高分辨率适配
完整代码实现
/*** Processing图像加载与高分辨率适配示例* 功能:加载背景图像并显示可交互的白色方块*/// 声明图像变量
PImage backgroundImg, characterImg;void setup() {// 设置画布尺寸为800x600像素size(800, 600);// === 像素密度配置 ===// 根据需求选择是否使用高像素密度boolean useHighDensity = true; // 设置为false可禁用高密度显示if (!useHighDensity) {pixelDensity(1); // 使用标准像素密度println("使用标准像素密度");} else {println("使用高像素密度: " + pixelDensity);}// === 加载背景图像 ===// 使用绝对路径加载图像文件backgroundImg = loadImage("D:/Users/KRATZDISTELN/Desktop/STEFANO/Processing/sketch/eyes.jpg");// 检查图像是否成功加载if (backgroundImg == null) {println("错误:无法加载背景图片!");// 创建白色背景作为备用方案backgroundImg = createImage(width, height, RGB);backgroundImg.loadPixels();for (int i = 0; i < backgroundImg.pixels.length; i++) {backgroundImg.pixels[i] = color(255); // 纯白色}backgroundImg.updatePixels();} else {println("背景图片加载成功!原始尺寸:" + backgroundImg.width + "x" + backgroundImg.height);}// === 创建角色图像 ===// 创建一个100x100像素的纯白色图像作为角色characterImg = createImage(100, 100, ARGB);characterImg.loadPixels();for (int i = 0; i < characterImg.pixels.length; i++) {characterImg.pixels[i] = color(255); // 纯白色}characterImg.updatePixels();// === 高密度屏幕适配 ===// 在高像素密度模式下调整图像尺寸if (pixelDensity > 1) {backgroundImg.resize(width * pixelDensity, height * pixelDensity);characterImg.resize(100 * pixelDensity, 100 * pixelDensity);println("已调整图像尺寸以适应高像素密度屏幕");}
}void draw() {// === 绘制背景 ===// 将背景图像绘制到画布上,填满整个窗口image(backgroundImg, 0, 0, width, height);// === 绘制交互角色 ===// 根据像素密度调整显示尺寸float displaySize = 100;if (pixelDensity > 1) {displaySize = 50; // 在高密度屏幕上显示较小尺寸}// 绘制白色方块作为可交互角色,位置跟随鼠标fill(255); // 设置填充色为纯白色noStroke(); // 不绘制边框rect(mouseX, mouseY, displaySize, displaySize);
}// 可选:添加鼠标交互功能
void mousePressed() {println("鼠标点击位置: (" + mouseX + ", " + mouseY + ")");
}// 可选:添加键盘交互功能
void keyPressed() {if (key == ' ') {// 空格键保存当前画面saveFrame("screenshot-######.png");println("画面已保存");}
}
代码详解
1. 变量声明与初始化
PImage backgroundImg, characterImg;
知识点:PImage
是Processing中专门用于处理图像的类,可以存储、操作和显示图像数据。
2. 画布设置与像素密度配置
size(800, 600);
boolean useHighDensity = true;
关键点:
size()
函数设置画布尺寸pixelDensity()
控制像素密度,解决高分辨率屏幕显示问题- 现代高分辨率屏幕默认使用
pixelDensity(2)
3. 图像加载与错误处理
backgroundImg = loadImage("D:/Users/KRATZDISTELN/Desktop/STEFANO/Processing/sketch/eyes.jpg");if (backgroundImg == null) {// 错误处理代码
}
重要技巧:
- 使用绝对路径时确保路径格式正确
- 始终检查图像是否成功加载
- 提供备用方案防止程序崩溃
第二部分:椭圆眼睛交互程序
完整代码实现
/*** 椭圆眼睛跟随鼠标交互程序* 功能:在背景图片上绘制多个椭圆眼睛,眼珠跟随鼠标移动* 使用仿射变换技术实现精确的椭圆内眼珠移动*/PImage img;void setup() {size(1280, 720, P2D);pixelDensity(1);// 加载背景图片img = loadImage("D:/Users/KRATZDISTELN/Desktop/STEFANO/Processing/sketch/eyes.jpg");img.resize(0, height);
}void draw() {// 绘制背景图片image(img, 0, 0, width, height);// 在图片上绘制眼睛drawEllipticalEye(161, 223, 60, 40, 0.3);drawEllipticalEye(223, 223, 60, 40, 0.3);drawEllipticalEye(561, 255, 60, 40, 0.3);drawEllipticalEye(718, 255, 60, 40, 0.3);drawEllipticalEye(1039, 183, 60, 40, 0.3);drawEllipticalEye(1101, 182, 60, 40, 0.3);
}// 自定义函数:绘制椭圆眼睛(使用仿射变换)
// 参数说明:
// - x, y: 眼睛中心坐标
// - a: 椭圆半长轴
// - b: 椭圆半短轴
// - pupilScale: 眼珠相对于眼睛大小的比例 (0-1)
void drawEllipticalEye(float x, float y, float a, float b, float pupilScale) {// 保存当前变换状态pushMatrix();// 将坐标系原点移动到眼睛中心translate(x, y);// 计算缩放比例,将椭圆变换为圆形float scaleX = 1.0;float scaleY = a / b; // 垂直方向缩放,使椭圆变为圆// 应用缩放变换scale(scaleX, scaleY);// 现在我们在一个圆形坐标系中工作// 眼睛的半径现在是 a (因为水平方向不变,垂直方向缩放后,原来的b变成了a)// 计算鼠标在变换后的坐标系中的位置float transformedMouseX = (mouseX - x) / scaleX;float transformedMouseY = (mouseY - y) / scaleY;// 计算从中心到变换后鼠标位置的距离float distance = dist(0, 0, transformedMouseX, transformedMouseY);// 计算眼珠位置float pupilX, pupilY;// 如果变换后的鼠标在圆内,眼珠直接跟随鼠标if (distance <= a - a * pupilScale) {pupilX = transformedMouseX;pupilY = transformedMouseY;} else {// 如果变换后的鼠标在圆外,眼珠停留在圆边界上float unitX = transformedMouseX / distance;float unitY = transformedMouseY / distance;pupilX = unitX * (a - a * pupilScale);pupilY = unitY * (a - a * pupilScale);}// 绘制眼睛(在变换后的坐标系中是一个圆)fill(255);stroke(0);strokeWeight(3 / scaleY); // 调整线宽以保持视觉一致性ellipse(0, 0, a * 2, a * 2); // 注意:现在是圆形// 绘制眼珠(在变换后的坐标系中是一个圆)fill(0);noStroke();ellipse(pupilX, pupilY, a * pupilScale * 2, a * pupilScale * 2);// 恢复变换状态popMatrix();
}
眼球跟随鼠标的数学算法
算法概述
眼球跟随鼠标但在眼眶内运动的算法核心是通过仿射变换将椭圆坐标系转换为圆形坐标系,从而简化计算。以下是该算法的数学描述:
数学符号定义
- EEE:椭圆眼睛,中心坐标为(xe,ye)(x_e, y_e)(xe,ye),半长轴为aaa,半短轴为bbb
- MMM:鼠标位置,坐标为(xm,ym)(x_m, y_m)(xm,ym)
- PPP:眼珠位置,坐标为(xp,yp)(x_p, y_p)(xp,yp)
- rpr_prp:眼珠半径,rp=a⋅pupilScaler_p = a \cdot \text{pupilScale}rp=a⋅pupilScale
算法步骤
步骤1:坐标系变换
通过仿射变换将椭圆EEE映射为单位圆:
缩放因子:sy=ab变换矩阵:T=[100sy]变换后鼠标位置:M′=(xm′,ym′)=(xm−xe1,ym−yesy) \begin{align} \text{缩放因子} &: s_y = \frac{a}{b} \\ \text{变换矩阵} &: T = \begin{bmatrix} 1 & 0 \\ 0 & s_y \end{bmatrix} \\ \text{变换后鼠标位置} &: M' = (x_m', y_m') = \left(\frac{x_m - x_e}{1}, \frac{y_m - y_e}{s_y}\right) \end{align} 缩放因子变换矩阵变换后鼠标位置:sy=ba:T=[100sy]:M′=(xm′,ym′)=(1xm−xe,syym−ye)
步骤2:距离计算
计算变换后鼠标位置到圆心的距离:
d=(xm′)2+(ym′)2 d = \sqrt{(x_m')^2 + (y_m')^2} d=(xm′)2+(ym′)2
步骤3:眼珠位置判定
根据距离ddd决定眼珠位置:
- 情况1:如果d≤a−rpd \leq a - r_pd≤a−rp(鼠标在安全区域内)
P′=(xp′,yp′)=(xm′,ym′) P' = (x_p', y_p') = (x_m', y_m') P′=(xp′,yp′)=(xm′,ym′)
- 情况2:如果d>a−rpd > a - r_pd>a−rp(鼠标在安全区域外)
单位向量:u=(xm′d,ym′d)眼珠位置:P′=(xp′,yp′)=u⋅(a−rp) \begin{align} \text{单位向量} &: \mathbf{u} = \left(\frac{x_m'}{d}, \frac{y_m'}{d}\right) \\ \text{眼珠位置} &: P' = (x_p', y_p') = \mathbf{u} \cdot (a - r_p) \end{align} 单位向量眼珠位置:u=(dxm′,dym′):P′=(xp′,yp′)=u⋅(a−rp)
步骤4:坐标逆变换
将眼珠位置变换回原始椭圆坐标系:
P=(xp,yp)=(xp′⋅1+xe,yp′⋅sy+ye) P = (x_p, y_p) = (x_p' \cdot 1 + x_e, y_p' \cdot s_y + y_e) P=(xp,yp)=(xp′⋅1+xe,yp′⋅sy+ye)
算法优势
- 数学简洁性:通过坐标变换将复杂的椭圆边界检测简化为圆形边界检测
- 计算效率:避免了求解椭圆方程的复杂计算
- 视觉自然性:眼珠移动轨迹符合物理直觉
几何解释
该算法本质上是在进行以下几何操作:
- 拉伸变换:将椭圆沿y轴拉伸为圆形
- 圆形边界检测:在圆形坐标系中计算眼珠位置
- 逆变换:将计算结果映射回原始椭圆形状
这种方法确保了眼珠始终停留在椭圆边界内部,同时保持与鼠标方向的自然对应关系。
第三部分:椭圆测量工具
完整代码实现
/*** 椭圆测量工具* 功能:帮助精确测量图片中眼睛的位置和尺寸参数* 使用方法:点击并拖动鼠标绘制椭圆,控制台输出参数代码*/PImage img;
boolean isMeasuring = false;
float startX, startY; // 测量起始点
float endX, endY; // 测量结束点
ArrayList<EllipseData> ellipses = new ArrayList<EllipseData>(); // 存储所有测量的椭圆void setup() {size(1280, 720);pixelDensity(1);// 加载背景图片img = loadImage("D:/Users/KRATZDISTELN/Desktop/STEFANO/Processing/sketch/eyes.jpg");if (img != null) {img.resize(0, height);}textSize(16);
}void draw() {// 绘制背景图片if (img != null) {image(img, 0, 0, width, height);} else {background(150, 200, 255);text("图片加载失败,请检查文件路径", 50, 50);}// 绘制所有已测量的椭圆for (EllipseData ellipse : ellipses) {drawEllipseMeasurement(ellipse);}// 绘制当前正在测量的椭圆if (isMeasuring) {drawCurrentMeasurement();}// 显示帮助信息displayHelp();
}void mousePressed() {if (mouseButton == LEFT) {// 开始测量isMeasuring = true;startX = mouseX;startY = mouseY;endX = mouseX;endY = mouseY;}
}void mouseDragged() {if (isMeasuring) {// 更新测量结束点endX = mouseX;endY = mouseY;}
}void mouseReleased() {if (mouseButton == LEFT && isMeasuring) {// 结束测量,保存椭圆数据isMeasuring = false;// 计算椭圆的中心、半长轴和半短轴float centerX = (startX + endX) / 2;float centerY = (startY + endY) / 2;float a = abs(endX - startX) / 2; // 半长轴float b = abs(endY - startY) / 2; // 半短轴// 添加到椭圆列表ellipses.add(new EllipseData(centerX, centerY, a, b));// 在控制台输出椭圆参数println("椭圆 " + ellipses.size() + ":");println(" 中心坐标: (" + centerX + ", " + centerY + ")");println(" 半长轴: " + a);println(" 半短轴: " + b);println(" 使用代码: drawEllipticalEye(" + centerX + ", " + centerY + ", " + a + ", " + b + ", 0.3);");println();}
}void keyPressed() {if (key == 'c' || key == 'C') {// 清除所有测量ellipses.clear();} else if (key == 'z' || key == 'Z' && !ellipses.isEmpty()) {// 撤销最后一个测量ellipses.remove(ellipses.size() - 1);}
}// 绘制当前正在测量的椭圆
void drawCurrentMeasurement() {float centerX = (startX + endX) / 2;float centerY = (startY + endY) / 2;float a = abs(endX - startX) / 2;float b = abs(endY - startY) / 2;// 绘制椭圆noFill();stroke(255, 0, 0);strokeWeight(2);ellipse(centerX, centerY, a * 2, b * 2);// 绘制中心点fill(255, 0, 0);ellipse(centerX, centerY, 6, 6);// 显示尺寸信息fill(255, 0, 0);text("半长轴: " + nf(a, 0, 1), centerX + a + 10, centerY);text("半短轴: " + nf(b, 0, 1), centerX, centerY + b + 20);
}// 绘制已测量的椭圆
void drawEllipseMeasurement(EllipseData ellipse) {// 绘制椭圆noFill();stroke(0, 255, 0);strokeWeight(2);ellipse(ellipse.x, ellipse.y, ellipse.a * 2, ellipse.b * 2);// 绘制中心点fill(0, 255, 0);ellipse(ellipse.x, ellipse.y, 6, 6);// 显示椭圆编号和参数fill(0, 255, 0);text(ellipse.id + ": (" + nf(ellipse.x, 0, 1) + ", " + nf(ellipse.y, 0, 1) + ")", ellipse.x + ellipse.a + 10, ellipse.y - 10);text("a=" + nf(ellipse.a, 0, 1) + ", b=" + nf(ellipse.b, 0, 1), ellipse.x + ellipse.a + 10, ellipse.y + 10);
}// 显示帮助信息
void displayHelp() {fill(255);stroke(0);strokeWeight(1);text("椭圆测量工具", 20, 30);text("1. 点击并拖动鼠标测量椭圆", 20, 50);text("2. 按 'C' 清除所有测量", 20, 70);text("3. 按 'Z' 撤销最后一个测量", 20, 90);text("4. 控制台会输出椭圆参数", 20, 110);// 显示鼠标坐标text("鼠标坐标: (" + mouseX + ", " + mouseY + ")", width - 200, 30);
}// 椭圆数据类
class EllipseData {float x, y; // 中心坐标float a, b; // 半长轴和半短轴int id; // 椭圆编号EllipseData(float x, float y, float a, float b) {this.x = x;this.y = y;this.a = a;this.b = b;this.id = ellipses.size() + 1;}
}
使用流程
- 准备阶段:安装Processing环境,准备背景图片
- 测量阶段:运行椭圆测量工具程序,在图片上点击并拖动鼠标测量每个眼睛的位置和尺寸
- 复制参数:从控制台复制生成的椭圆参数代码
- 实现交互:将参数代码粘贴到椭圆眼睛绘制程序的draw()函数中
- 运行程序:运行椭圆眼睛绘制程序,享受交互效果
进一步学习:
- Processing官方文档:https://processing.org/reference/
- PImage类详细说明:https://processing.org/reference/PImage.html
- 仿射变换原理:https://en.wikipedia.org/wiki/Affine_transformation