[疯狂Java]AWT剪切板:图像传递

本文介绍如何在手绘程序中利用系统剪切板传递图像,实现图像的复制与粘贴功能,通过自定义Transferable类封装图像数据,结合图层技术在手绘图像上进行图像的叠加与覆盖。

1. 自己实现传递的图像的Transferable类:

    1) JDK只给Transferable接口提供了StringSelection的实现类(即JDK只默认实现了文本传递),但没有提供图像传递类的实现,主要是因为图像格式千变万化,处理起来各不相同,因此JDK只是在DataFlavor中保留了一个标签imageFlavor用以让用户自己实现传递图像的Transferable类;

    2) 从格式标签的角度理解Transferable类:它不仅仅包装了传递的数据,并处理了一些同步操作、和系统底层的交流,最重要的是它把数据格式记录在了里面(即Flavor标签),将其放入剪切板的同时也将格式标签也写入了其中,这样在取数据的时候就不会不知道在剪切板里面的是什么东西了;

    3) 要实现一个支持图像传递的Transferable类(例如自己写一个ImageSelection类)就必须要实现Transferable接口并实现里面所有的接口方法,可以看一下示例:

class ImageSelection implements Transferable { // 必须实现Transferable接口
	
	private Image img; // 里面必须要包装数据本身

	public ImageSelection(Image img) {
		this.img = img;
	}
	
	@Override // 返回所有支持的格式类型
	public DataFlavor[] getTransferDataFlavors() {
		// TODO Auto-generated method stub
		// return null;
		return new DataFlavor[] { DataFlavor.imageFlavor };
	}

	@Override // 判断一个格式是否支持
	public boolean isDataFlavorSupported(DataFlavor flavor) {
		// TODO Auto-generated method stub
		// return false;
		return flavor.equals(DataFlavor.imageFlavor); // 只支持图像一种格式
	}

	@Override // 获取其中的数据
	public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
		// TODO Auto-generated method stub
		// return null;
		if (flavor.equals(DataFlavor.imageFlavor)) { // 取数据时必须要保证格式符合要求
			return img;
		}
		else { // 否则就抛出格式不支持异常
			throw new UnsupportedFlavorException(flavor);
		}
	}
	
}
!!必须要实现的三个方法做了详细的注释;

!!!有了imageFlavor格式标签,在使用Clipboard的setContents时Java就会进行一些加速优化处理,这个是imgeFlavor所带来的福利,都隐藏在该格式标签中了(在底层实现了);


2. 示例:利用系统剪切板传递图像

    1) 该示例是之前手绘程序Hand-Draw的扩展;

    2) 其目标就是可以将手绘内容(只将手绘内容复制到系统剪切板中)复制到系统剪切板中,这样就可以将手绘的内容粘贴到其它程序中了(可以测试);

    3) 其次就是可以将其它程序中的图像粘贴到当前手绘程序中并覆盖到当前手绘图像的上面(这里就用到了图层的技巧);

    4) 程序:

class ImageSelection implements Transferable { // 必须实现Transferable接口
	
	private Image img; // 里面必须要包装数据本身

	public ImageSelection(Image img) {
		this.img = img;
	}
	
	@Override // 返回所有支持的格式类型
	public DataFlavor[] getTransferDataFlavors() {
		// TODO Auto-generated method stub
		// return null;
		return new DataFlavor[] { DataFlavor.imageFlavor };
	}

	@Override // 判断一个格式是否支持
	public boolean isDataFlavorSupported(DataFlavor flavor) {
		// TODO Auto-generated method stub
		// return false;
		return flavor.equals(DataFlavor.imageFlavor); // 只支持图像一种格式
	}

	@Override // 获取其中的数据
	public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
		// TODO Auto-generated method stub
		// return null;
		if (flavor.equals(DataFlavor.imageFlavor)) { // 取数据时必须要保证格式符合要求
			return img;
		}
		else { // 否则就抛出格式不支持异常
			throw new UnsupportedFlavorException(flavor);
		}
	}
	
}

public class AwtTest {
	
	private Frame f = new Frame("Hand-Draw-Clipboard");

	private final int DRAW_WIDTH = 500;
	private final int DRAW_HEIGHT = 400;
	private DrawCanvas drawArea = new DrawCanvas();
	private BufferedImage bufImg = new BufferedImage(DRAW_WIDTH, DRAW_HEIGHT, BufferedImage.TYPE_INT_RGB);
	private Graphics gmm = bufImg.getGraphics();
	private Color foreColor = new Color(255, 0, 0); // 当前画笔的颜色
	class DrawCanvas extends Canvas {

		@Override
		public void paint(Graphics g) { // 通过位图传送的方式将图像传送至画布在屏幕上显示
		// 因此我们的手绘程序其实并不是直接在屏幕上手绘,而是现在内存中手绘,然后传送至屏幕
			// TODO Auto-generated method stub
			// super.paint(g);
			g.drawImage(bufImg, 0, 0, null);
			for (Image img: imgList) { // 图层覆盖,让外面的图片覆盖在手绘图像上面
				g.drawImage(img, 0, 0, null);
			}
		}

	}
	
	private PopupMenu pop = new PopupMenu();
	private MenuItem red = new MenuItem("red");
	private MenuItem green = new MenuItem("green");
	private MenuItem blue = new MenuItem("blue");
	
	// 上一次鼠标拖动事件发生时鼠标的位置
	private int preX = -1;
	private int preY = -1;
	
	// 复制粘贴按钮
	private Panel p = new Panel();
	// 复制是将手绘内容复制到系统剪切板中
	private Button btnCopy = new Button("Copy");
	// 粘贴是将其它程序放到剪切板中的内容粘贴到当前手绘图像之上
	private Button btnPaste = new Button("Paste");
	
	// 图层列表,每次从剪切板中去除的图像都添加到该列表后面,粘贴的时候按照先后顺序粘贴
	// 这样就能保证外面的图片覆盖到手绘图像上层
	private List<Image> imgList = new ArrayList<Image>();
	private Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
	
	public void init() {
		f.addWindowListener(new WindowAdapter() {

			@Override
			public void windowClosing(WindowEvent e) {
				// TODO Auto-generated method stub
				// super.windowClosing(e);
				System.exit(0);
			}
			
		});
		
		drawArea.addMouseMotionListener(new MouseMotionAdapter() {

			@Override
			public void mouseDragged(MouseEvent e) {
				// TODO Auto-generated method stub
				// super.mouseDragged(e);
				if (preX > 0 && preY > 0) { // 如果上次鼠标拖动的终点已存在
					gmm.setColor(foreColor); // 那就直接在内存中画线(要先设置当前颜色,可能已经通过右键菜单改变)
					gmm.drawLine(preX, preY, e.getX(), e.getY());
				}
				
				// 将本次拖拽的终点记录下来
				preX = e.getX();
				preY = e.getY();
				
				drawArea.repaint(); // 画好后进行位图传送
				// 这里的策略是画一条传送一次,效率仍然很低,因为要求实时显示画线轨迹,这里只是为了演示位图传送而已
			}
			
		});
		
		drawArea.addMouseListener(new MouseAdapter() {

			@Override
			public void mouseReleased(MouseEvent e) {
				// TODO Auto-generated method stub
				// super.mouseReleased(e);
				if (e.isPopupTrigger()) {
					pop.show(drawArea, e.getX(), e.getY());
				}
				
				// 鼠标松开意味着可能发生了两种事件的一种
				// 第一种就是右键松开,表示弹出右键菜单,即上面的代码
				// 另一种就是画图键(左键)松开,表示一段手绘的结束
				// 这两种事件都意味着一段手绘的结束,为了重新开始下一段手绘要把preX和preY初始化
				preX = -1;
				preY = -1;
			}
			
		});
		
		ActionListener menuListener = e -> {
			if (e.getActionCommand().equals("red")) {
				foreColor = new Color(255, 0, 0);
			}
			else if (e.getActionCommand().equals("green")) {
				foreColor = new Color(0, 255, 0);
			}
			else if (e.getActionCommand().equals("blue")) {
				foreColor = new Color(0, 0, 255);
			}
		};
		red.addActionListener(menuListener);
		green.addActionListener(menuListener);
		blue.addActionListener(menuListener);
		
		btnCopy.addActionListener(e -> { // 手绘内容复制出去
			ImageSelection cont = new ImageSelection(bufImg);
			cb.setContents(cont, null);
		});
		btnPaste.addActionListener(e -> { // 外面的图像粘贴进来
			if (cb.isDataFlavorAvailable(DataFlavor.imageFlavor)) {
				try { // 添加图层
					imgList.add((Image)cb.getData(DataFlavor.imageFlavor));
					drawArea.repaint();
				}
				catch (Exception ecp) {
					ecp.printStackTrace();
				}
			}
		});
		
		f.add(drawArea);
			drawArea.setPreferredSize(new Dimension(DRAW_WIDTH, DRAW_HEIGHT));
			gmm.fillRect(0, 0, DRAW_WIDTH, DRAW_HEIGHT); // 初始时默认背景色为白色
			drawArea.add(pop);
				pop.add(red);
				pop.add(green);
				pop.add(blue);
		f.add(p, BorderLayout.SOUTH);
			p.add(btnCopy);
			p.add(btnPaste);
				
		f.pack();
		f.setVisible(true);
	}

	public static void main(String[] args) {
		new AwtTest().init();
	}

}
!改动点:

//复制粘贴按钮
private Panel p = new Panel();
// 复制是将手绘内容复制到系统剪切板中
private Button btnCopy = new Button("Copy");
// 粘贴是将其它程序放到剪切板中的内容粘贴到当前手绘图像之上
private Button btnPaste = new Button("Paste");
	
// 图层列表,每次从剪切板中去除的图像都添加到该列表后面,粘贴的时候按照先后顺序粘贴
// 这样就能保证外面的图片覆盖到手绘图像上层
private List<Image> imgList = new ArrayList<Image>();
private Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();

class DrawCanvas extends Canvas {

	@Override
	public void paint(Graphics g) { // 通过位图传送的方式将图像传送至画布在屏幕上显示
	// 因此我们的手绘程序其实并不是直接在屏幕上手绘,而是现在内存中手绘,然后传送至屏幕
		// TODO Auto-generated method stub
		// super.paint(g);
		g.drawImage(bufImg, 0, 0, null);
		for (Image img: imgList) { // 图层覆盖,让外面的图片覆盖在手绘图像上面
			g.drawImage(img, 0, 0, null);
		}
	}

}

btnCopy.addActionListener(e -> { // 手绘内容复制出去
	ImageSelection cont = new ImageSelection(bufImg);
	cb.setContents(cont, null);
});
btnPaste.addActionListener(e -> { // 外面的图像粘贴进来
	if (cb.isDataFlavorAvailable(DataFlavor.imageFlavor)) {
		try { // 添加图层
			imgList.add((Image)cb.getData(DataFlavor.imageFlavor));
			drawArea.repaint();
		}
		catch (Exception ecp) {
			ecp.printStackTrace();
		}
	}
});

f.add(drawArea);
	drawArea.setPreferredSize(new Dimension(DRAW_WIDTH, DRAW_HEIGHT));
	gmm.fillRect(0, 0, DRAW_WIDTH, DRAW_HEIGHT); // 初始时默认背景色为白色
	drawArea.add(pop);
		pop.add(red);
		pop.add(green);
		pop.add(blue);
f.add(p, BorderLayout.SOUTH);
	p.add(btnCopy);
	p.add(btnPaste);




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值