Arava: 用 swing 写一个取色器


备哥以前发我了一个小工具,TakeColor 取色器。来复刻一个吧。

1584410231478

分析一下:顶部是菜单,左侧框显示当前鼠标所在的颜色(下面显示当前坐标和颜色值,默认RGB),中间框显示鼠标周围区域,右侧显示取色的列表(按alt+c)取色。下面一行是颜色值,并且可以切换不同的模式。打开调色盘会显示底部的颜色框和调节滑块。

TakeColor v1.0

先来实现一个简单的屏幕取色。

鼠标的移动监听器中的 moved 和 dragged。moved 只在组件内有效,dragged 是拖动鼠标才产生的事件。在 中发现了可以获取鼠标位置和颜色的代码:

Point mousepoint = MouseInfo.getPointerInfo().getLocation();
Color pixel = robot.getPixelColor(mousepoint.x, mousepoint.y);

再加上定时器,就可以即时获取鼠标所在点像素的颜色了。

takecolor_v1

代码如下:

import java.awt.EventQueue;
import java.awt.Point;
import java.awt.Dimension;
import java.awt.Color;
import java.awt.Toolkit;
import java.awt.MouseInfo;
import java.awt.AWTException;
import java.awt.Robot;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.util.Timer;
import java.util.TimerTask;

class ColorFrame extends JFrame{

    private static final long serialVersionUID = 1L;
    private static final int WIDTH = 350;
    private static final int HEIGHT = 200;
    private JPanel mainPanel;
    private Robot robot;

    public ColorFrame(){
        // 窗体设置
        //setSize(WIDTH, HEIGHT);
        // 窗体居中
        Dimension centre = this.getScreenCentre();
        setLocation(centre.width - WIDTH/2, centre.height - HEIGHT/2);

        setTitle("TakeColor");
        setUndecorated(true); // 关闭所有框架装饰

        // 添加 panel
        mainPanel = new JPanel();
        add(mainPanel);
        pack();

        // 鼠标监听
        mouseListener();
    }

    /**
     * 鼠标监听,利用 timer
     */
    public void mouseListener() {
        try{
            robot = new Robot();
        }catch(AWTException e){
            e.printStackTrace();
        }

        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                setPanelColor();
            }
        }, 100, 100);
    }

    /**
     * 获取位置和颜色,并设置panel颜色
     */
    private void setPanelColor(){
        Point point = MouseInfo.getPointerInfo().getLocation();
        Color pixel = robot.getPixelColor(point.x, point.y);
        System.out.println("Location:x=" + point.x + ", y=" + point.y + "\t" + pixel);
        mainPanel.setBackground(pixel);
    }
    
    /**
     * 获取屏幕中心点
     * @return Dimension
     */
    public Dimension getScreenCentre(){
        // 获取屏幕分辨率
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        return new Dimension(screenSize.width/2, screenSize.height/2);
    }

    /**
     * 重置窗口大小
     */
    public Dimension getPreferredSize(){
        return new Dimension(WIDTH, HEIGHT);
    }
}

public class TakeColor{
    public static void main(String [] args){
        EventQueue.invokeLater(() -> {
            ColorFrame f = new ColorFrame();
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 设置关闭事件
            f.setVisible(true); // 显示窗体
        });
    }
}

之后更改了代码结构,main() 调用 frame, frame 只 add(panel) & pack(),主要操作都在 JPanel 中进行。

TakeColor v1.1

这一版显示坐标和颜色值,放大鼠标周围区域。

1. 显示颜色、位置以及颜色值

用一个 JPanel 设置背景颜色来显示当前颜色,两个 JLable 用来显示当前位置和颜色值。(为了节省计算量,鼠标在移动之前不重新获取颜色和放大区域。)

1584608287035

2. 放大镜

放大镜的功能,本来打算使用 Color 数组存储鼠标周围区域像素的Color,然后放大的话,每个像素放大成四个点。但是使用如下面这种代码,每个像素点都获取颜色getPixelColor()会很慢。

for(int i=0; i<10; i++){
    for(int j=0; j<10; j++){
        areaColor[i][j] = robot.getPixelColor(x+i, y+i);
    }
}

找其他的不用动像素的办法:使用Java实现截取电脑屏幕的功能中发现可以屏幕截取然后展示

// 截取图形,输出
Image image = robot.createScreenCapture(new Rectangle(0, 0, width, height));
BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = bi.createGraphics();
g.drawImage(image, 0, 0, width, height, null);
2.1 屏幕动态截图效果

为了直接将image对象显示到界面上,我们可以覆盖 JComponent 类的 paintCompoent(),得到 Graphics 变量用来绘制。因为 JPanel 继承了 JComponent, 所以同样可以用 paintCompoent()

class TakeColorPanel extends JPanel{
	/**
	 * 执行的方法
	 */
	public function xxx(){
    	// 截取以光标所在点为中心点,宽高为100像素的屏幕图像
    	int x = mousePoint.x;
        int y = mousePoint.y;
        Rectangle r = new Rectangle (x-50, y-50, 100, 100);
        areaImage = robot.createScreenCapture(r);
        
        repaint(); // 重绘,调用 paintComponent
    }
	
	/**
     * 绘制界面
     */
    public void paintComponent(Graphics g){
        Graphics2D g2 = (Graphics2D) g;
        g2.drawImage(areaImage,120,10,null); // 绘制到中间区域
    }

}

展示屏幕截图的效果:ROOM·屠宰场

room.png

动图:

room.png

真的像特拉法尔加·罗的能力

ROOM·屠宰场

能在自己制造的半球状/全球状空间里进行物件转换,能够将物体或是生物切成数块后在他控制的范围内重新组合,被分离的生物不会死、不痛、也不会流血。此外,罗可以借着此技能帮助特定伤患清除体内的有害物质,达到治疗伤患的效果。也能够进行瞬间移动,进入常理下无法进入的建筑物内。

2.2 放大

发现 drawImage() 就可以放大图片。

drawImage.png

对于50*50像素的图片,使用下面的代码, 第二行会显示放大1倍的效果(变成了100*100)

g2.drawImage(areaImage,120,10,null);
g2.drawImage(areaImage,180,10,100,100,null);

drawImageCompare.png

放大效果完成。之后添加边框,和原版对比的效果如下:

zoom.png

TakeColor v1.2

这一版添加按键记录颜色历史。

但是没有在颜色添加上颜色值(JLabel样式好像有边框,设置的高度总是不对,不会调),直接用填充矩形来展示颜色历史:

// 右侧用矩形填充颜色
colorRecordRect.setFrameFromDiagonal(230,10+20*i,230+100,10+20*(i+1));
g2.draw(colorRecordRect);
g2.fill(colorRecordRect);

colorRecord.png

第二天发现 JLabel 高度是好着, 我将 setBounds() 的后两个参数宽高,写成右下角坐标了。所以改用 JLabel 来设置背景色和显示文字:

private JLabel colorRecordValue[] = new JLabel[colorRecordMax];
......
// label 组件添加
// 右侧的颜色背景和颜色值label。位置和大小230, 10, 100, 100
for(int i=0; icolorRecordMax)
		break;
}

注意,设置 JLabel 背景颜色的时候需要设置背景不透明 jlabel.setOpaque(true);

文字颜色值不能一直是黑色,不然黑色背景就会看不到。这里取反色,即 Color(255-R, 255-G, 255-B) 的值。

效果如下:

tkv1.2.png

TakeColor v1.3

这一版添加颜色模式的 select 框、颜色输入框和复制。

下拉框

使用下拉框放置颜色模式,获取选项时可以直接使用 colorModeCombo.getSelectedItem() 而不是书中这种根据索引再获取 colorModeCombo.getItemAt(colorModeCombo.getSelectedIndex()),书中应该是用来展示各种用法的。

将一个字符串赋值给枚举值时,需要使用 valueOf 方法将字符串转为枚举值,参考如何判断枚举和字符串相等(最简便方法):

enum A {  
	a, b, c, d;  
}  

A aa = A.valueOf(A.class, "a");  
System.out.println(aa == A.a);  

所以当选项改变,需要改变当前颜色模式:(selete 选项值转 String 是因为 getSlectedItem() 返回的是 current selected Object )

colorModeCombo.addActionListener(event -> 
{
    currentColorMode = ColorMode.valueOf((String)colorModeCombo.getSelectedItem());
});

颜色框和按钮都是常规的,访问剪切板参考Java操作系统剪贴板(Clipboard)实现复制和粘贴 。

我们的复刻取色器项目到这里就完成了,菜单不想加了,因为菜单加上去也最多加个放大倍数的效果,意义不大了。

最终成果:上面是原工具,下面是复刻版

takecolor.png

代码 git

Tips: 控制台编译时如果出现编码错误,可以指定编码编译javac -encoding utf-8 TakeColor.java