摘要:本文展示了如何使用Java的Robot類創建一個能夠捕獲主屏幕內容的屏幕捕捉程序。本程序處理捕捉屏幕外,還能夠將整個屏幕或者屏幕的一部分保存成.jpg文件。
java.awt.Robot類真的很好玩。玩Robot會給你帶來很多樂趣。下面讓我們看看用Robot是怎么創建你自己的屏幕捕捉程序的。
程序界面
下圖是我的屏幕捕捉程序的界面。你可以用它來捕捉屏幕,裁剪選取的圖片內容,并將結果保存為.jpg文件。

屏幕捕捉程序的界面很簡單,包括一個菜單和一個用于顯示捕捉圖像的滾動窗口。在窗口上按住鼠標左鍵拖動鼠標,你會看到一個紅色的虛線矩形,這就是圖像選取框,選取圖片以后選擇裁剪菜單,就可以對圖像進行裁減。見下圖:

系統實現
本程序包括3個源文件:
Capture.java:啟動應用程序,構建GUI;
ImageArea.java:顯示和選取屏幕圖片的組件;
ImageFileFilter.java:文件選擇過濾器,限制只能選擇JPEG文件。
下面我將結合代碼片斷介紹如何實現屏幕捕捉程序。
屏幕捕獲
要想使用Robot類創建屏幕捕捉程序,首先要創建Robot對象。在Capture類的
public static void main(String [] args)方法中用Robot的public Robot()創建Robot對象。如果創建成功,就會返回配置主屏幕坐標系統的Robot引用。如果平臺不支持低級控制,就會拋出java.awt.AWTException。如果平臺不允許創建Robot,就會拋出java.lang.SecurityException。希望在你的平臺上不會拋出上面兩個異常。
假設Robot對象已經創建,main()會調用Capture類的構造函數創建GUI。在創建GUI的同時,Capture調用dimScreenSize = Toolkit.getDefaultToolkit ().getScreenSize ();來獲得屏幕的尺寸信息。因為Robot中用于執行屏幕捕獲的public BufferedImage createScreenCapture(Rectangle screenRect)方法需要一個java.awt.Rectangle參數。構造函數通過rectScreenSize = new Rectangle (dimScreenSize);將java.awt.Dimension轉換成Rectangle 。下面的代碼片斷展示了當菜單中捕捉菜單項按下后執行的屏幕捕捉動作。
//?執行屏幕捕捉時隱藏屏幕捕捉程序,使其不顯示在桌面上.

setVisible?(false);

//?執行屏幕捕捉.

BufferedImage?biScreen;
biScreen?=?robot.createScreenCapture?(rectScreenSize);

//?完成屏幕捕捉后顯示屏幕捕捉程序窗口.

setVisible?(true);

//?用捕獲的屏幕圖片刷新ImageArea組件并相應調整滾動條.

ia.setImage?(biScreen);

jsp.getHorizontalScrollBar?().setValue?(0);
jsp.getVerticalScrollBar?().setValue?(0);

我不希望屏幕捕捉程序窗口遮住主屏幕,因此在捕捉屏幕之前我先將其隱藏。當獲取到保存有屏幕圖片象素的
java.awt.image.BufferedImage 后,屏幕捕捉程序窗口再次顯現出來并通過ImageArea組件顯示出
BufferedImage 。
圖像選取
首先我們需要一個矩形框來標識我們想要裁剪的圖像區域。在ImageArea組建中,提供了創建、操作、繪制這個矩形框的代碼。下面的代碼片斷來自ImageArea.java,ImageArea類的構造函數創建了選取矩形框的實例,并且創建了
java.awt.BasicStroke 和
java.awt.GradientPaint
對象來定義選取框的外觀,同時還注冊了鼠標和鼠標移動監聽,來幫助你操作矩形選取框。
//?創建矩形選取框.最好是只創建一個選取框,
???// 這樣比每次paintComponent()被調用時創建選取框好.
???// 減少了不必要的對象的創建

rectSelection?=?new?Rectangle?();

//?定義矩形選取框的輪廓.

bs?=?new?BasicStroke?(5,?BasicStroke.CAP_ROUND,?BasicStroke.JOIN_ROUND,

??????????????????????0,?new?float?[]?
{?12,?12?},?0);

//?定義矩形選取框的顏色

gp?=?new?GradientPaint?(0.0f,?0.0f,?Color.red,?1.0f,?1.0f,?Color.white,
????????????????????????true);

// 建立鼠標監聽.

MouseListener?ml;
ml?=?new?MouseAdapter?()

?????
{
?????????public?void?mousePressed?(MouseEvent?e)

?????????
{
????????????//?沒有捕獲的圖像時直接返回.

????????????if?(image?==?null)
????????????????return;

????????????destx?=?srcx?=?e.getX?();
????????????desty?=?srcy?=?e.getY?();

????????????repaint?();
?????????}
?????};
addMouseListener?(ml);

//?建立鼠標移動監聽.

MouseMotionListener?mml;
mml?=?new?MouseMotionAdapter?()

??????
{
??????????public?void?mouseDragged?(MouseEvent?e)

??????????
{
?????????????//?沒有捕獲的圖像時直接返回.

?????????????if?(image?==?null)
?????????????????return;

?????????????destx?=?e.getX?();
?????????????desty?=?e.getY?();

?????????????repaint?();?
??????????}
??????};
addMouseMotionListener?(mml);

當點擊鼠標時,鼠標事件處理器將
destx?和
srcx都設置為鼠標的橫坐標,縱坐標業一樣。源坐標和目的坐標一樣意味著刪除當前的矩形選取框。我們可以通過調用repaint(),repaint()會調用public void paintComponent(Graphics g)實現刪除矩形選取框。paintComponent()方法會比較srcx與destx
, srcy
與desty;如果他們不相等,就繪制一個新的矩形選取框。
//?繪制矩形選取框.

if?(srcx?!=?destx?||?srcy?!=?desty)


{
????//?計算左上和右下點的坐標.

????int?x1?=?(srcx?<?destx)???srcx?:?destx;
????int?y1?=?(srcy?<?desty)???srcy?:?desty;

????int?x2?=?(srcx?>?destx)???srcx?:?destx;
????int?y2?=?(srcy?>?desty)???srcy?:?desty;

????//?確定矩形的原點.

????rectSelection.x?=?x1;
????rectSelection.y?=?y1;

????//?確定矩形的長寬.

????rectSelection.width?=?(x2-x1)+1;
????rectSelection.height?=?(y2-y1)+1;

????//?繪制矩形.

????Graphics2D?g2d?=?(Graphics2D)?g;
????g2d.setStroke?(bs);
????g2d.setPaint?(gp);
????g2d.draw?(rectSelection);
}

在繪制矩形之前,要先確定矩形的左上和右下角,以便算出矩形的原點和長寬。因此你可以任意拖動鼠標繪制選取框,最后
srcx/
destx
和
srcy
/
desty中的最小值確定左上角,同理,最大值確定右下角。
圖像裁剪
選取完圖像以后,我們自然要裁剪它。當點擊菜單中的“裁剪”菜單項時,ImageArea組件會將選中的圖像裁減下來。如果裁剪成功,ImageArea的滾動條會重新設置,否則程序會彈出“超出范圍”對話框。
//?如果裁剪成功,重新設置滾動條.

if?(ia.crop?())


{
????jsp.getHorizontalScrollBar?().setValue?(0);
????jsp.getVerticalScrollBar?().setValue?(0);
}
else
????showError?("超出范圍");
也許你會問為什么會出現“超出范圍”呢?請看下圖

由于裁剪后GUI窗口不會改變大小,因此可能裁減后的圖片比程序的GUI窗口小,GUI窗口的背景就顯露出來。如果選取框包含了GUI窗口的背景象素,那么在裁剪時就會報“超出范圍”錯誤。
裁剪功能是由ImageArea的
public boolean crop() 方法實現的。當裁剪成功或者沒有可裁減圖像時返回true,如果選取框包含有GUI窗口背景象素,就返回false。下面是代碼:
public?boolean?crop?()


{
???//?如果選取框只是一個點,返回true

???if?(srcx?==?destx?&&?srcy?==?desty)
???????return?true;

???//?默認返回true.

???boolean?succeeded?=?true;

???//?計算選取框的左上角和右下角坐標.

???int?x1?=?(srcx?<?destx)???srcx?:?destx;
???int?y1?=?(srcy?<?desty)???srcy?:?desty;

???int?x2?=?(srcx?>?destx)???srcx?:?destx;
???int?y2?=?(srcy?>?desty)???srcy?:?desty;

???//?計算選取框的尺寸.

???int?width?=?(x2-x1)+1;
???int?height?=?(y2-y1)+1;

???//?創建保存裁剪圖像的圖像緩沖.

???BufferedImage?biCrop?=?new?BufferedImage?(width,?height,
?????????????????????????????????????????????BufferedImage.TYPE_INT_RGB);
???Graphics2D?g2d?=?biCrop.createGraphics?();

???//?執行裁剪操作.

???try

???
{
???????BufferedImage?bi?=?(BufferedImage)?image;
???????BufferedImage?bi2?=?bi.getSubimage?(x1,?y1,?width,?height);
???????g2d.drawImage?(bi2,?null,?0,?0);
???}
???catch?(RasterFormatException?e)

???
{
??????succeeded?=?false;
???}

???g2d.dispose?();

???if?(succeeded)
???????setImage?(biCrop);?
???else

???
{
???????//?準備刪除選取框.

???????srcx?=?destx;
???????srcy?=?desty;

???????//?刪除選取框.

???????repaint?();
???}

???return?succeeded;
}

crop()會調用
BufferedImage的
public BufferedImage getSubimage(int x, int y, int w, int h)方法將選取框中的圖像從原圖像中裁剪下來。如果參數不是指定的BufferedImage區域,此方法會拋出java.awt.image.RasterFormatException 異常,返回fasle。
保存圖像
本程序允許保存圖像。你可以通過文件選擇存對話框為要報存的圖像取個名字。文件保存對話框在Capture類的構造函數中定義。
final?JFileChooser?fcSave?=?new?JFileChooser?();
fcSave.setCurrentDirectory?(new?File?(System.getProperty?("user.dir")));
fcSave.setAcceptAllFileFilterUsed?(false);
fcSave.setFileFilter?(new?ImageFileFilter?());

為了約束文件選擇對話框所能保存只能保存JPEG文件,我們創建了一個ImageFileFilter類作為文件選擇對話框的文件過濾器。如果傳入方法public boolean accept (File f)的參數不是目錄或者以.jpg .jpeg為后綴的文件,那么返回false
public?boolean?accept?(File?f)


{
???//?允許用戶選擇文件夾.

???if?(f.isDirectory?())
???????return?true;

???//?允許用戶選擇以.jpg?或?.jpeg為后綴的文件

???String?s?=?f.getName?();
???int?i?=?s.lastIndexOf?('.');

???if?(i?>?0?&&?i?<?s.length?()-1)

???
{
???????String?ext?=?s.substring?(i+1).toLowerCase?();

???????if?(ext.equals?("jpg")?||?ext.equals?("jpeg"))
???????????return?true;
???}

???//?沒有可以選擇的.

???return?false;
}

當點擊菜單的“另存為”菜單項時,調用文件選擇器。文件選擇器會確保你的文件保存為JPEG文件。如果你為文件氣的名字已經被另一個文件使用,文件選擇器將詢問你是否覆蓋原文件。
//?顯示文件選擇器,不選中任何文件.
//?如果用戶點擊cancel,則退出.

fcSave.setSelectedFile?(null);
if?(fcSave.showSaveDialog?(Capture.this)?!=
????JFileChooser.APPROVE_OPTION)
????return;

//?獲取選擇的文件.如果不是以.jpg 或.jpeg為后綴,
?//?添加.jpg后綴

File?file?=?fcSave.getSelectedFile?();
String?path?=?file.getAbsolutePath?().toLowerCase?();
if?(!path.endsWith?(".jpg")?&&?!path.endsWith?(".jpeg"))
????file?=?new?File?(path?+=?".jpg");

//?如果文件已存在,通知用戶
??????????????????
if?(file.exists?())


{
????int?choice?=??JOptionPane.
??????????????????showConfirmDialog?(null,
?????????????????????????????????????"Overwrite?file?",
?????????????????????????????????????"Capture",
?????????????????????????????????????JOptionPane.
?????????????????????????????????????YES_NO_OPTION);
????if?(choice?==?JOptionPane.NO_OPTION)
????????return;
}

如果文件不存在,或者你允許覆蓋已有文件,程序將會保存圖片。為了完成保存,我們使用了Java的ImageIO框架。代碼如下:
ImageWriter?writer?=?null;
ImageOutputStream?ios?=?null;

try


{
????//?獲得一個jpeg?類型的寫入器

????Iterator?iter;
????iter?=?ImageIO.getImageWritersByFormatName?("jpeg");

????//?驗證寫入器是否存在

????if?(!iter.hasNext?())

????
{
????????showError?("Unable?to?save?image?to?jpeg?file?type.");
????????return;
????}.

????writer?=?(ImageWriter)?iter.next();


????//?獲取寫入器寫入目標

????ios?=?ImageIO.createImageOutputStream?(file);
????writer.setOutput?(ios);

????//?設置jpeg壓縮率為?95%.

????ImageWriteParam?iwp?=?writer.getDefaultWriteParam?();
????iwp.setCompressionMode?(ImageWriteParam.MODE_EXPLICIT);
????iwp.setCompressionQuality?(0.95f);

????//?寫入圖像.

????writer.write?(null,
??????????????????new?IIOImage?((BufferedImage)
????????????????????????????????ia.getImage?(),?null,?null),
??????????????????iwp);
}
catch?(IOException?e2)


{
????showError?(e2.getMessage?());
}
finally


{
????try

????
{
????????//?清理.

????????if?(ios?!=?null)

????????
{
????????????ios.flush?();
????????????ios.close?();
????????}

????????if?(writer?!=?null)
????????????writer.dispose?();
????}
????catch?(IOException?e2)

????
{
????}
}

保存后的清理工作很有必要。我將清理代碼上到finally塊中,這樣不管是正常的保存成功還是意外地中止,都能執行響應的清理工作。
改進
本文中的這個屏幕捕捉程序只能捕捉主屏幕設備的圖像。也許你希望捕獲所有屏幕的圖像。要實現這個功能,你可以將下面的代碼加入到Capture.java中:
GraphicsEnvironment?graphenv?=?GraphicsEnvironment.getLocalGraphicsEnvironment?();
GraphicsDevice?[]?screens?=?graphenv.getScreenDevices?();
BufferedImage?[]?captures?=?new?BufferedImage?[screens.length];

for?(int?i?=?0;?i?<?screens.length;?i++)


{
????DisplayMode?mode?=?screens?[i].getDisplayMode?();
????Rectangle?bounds?=?new?Rectangle?(0,?0,?mode.getWidth?(),?mode.getHeight?());
????captures?[i]?=?new?Robot?(screens?[i]).createScreenCapture?(bounds);
}

上面介紹了用Robot類制作Java屏幕捕捉程序的全過程。希望對大家能有所啟示。如果需要本程序的全部源代碼,請留下郵箱,我會及時發給你。
posted on 2006-05-17 11:30
學二的貓 閱讀(5530)
評論(72) 編輯 收藏 所屬分類:
Java禪機