Java實現圖片內容無損任意角度旋轉

主要問題是如何在圖片作旋轉後計算出新圖片的長寬。html

在java 2d和基本math庫的幫助下,其實利用簡單的計算就能夠知道。java


如下算法只是計算出旋轉小於90度時的公式。當旋轉大於90時,能夠先把問題域換算到銳角的狀況,再進行計算便可。git

以下圖所示,須要計算出來的是len_delta的長度,就是有雙豎線的位置,它是新圖片要增長的寬。(要增長的高度同理可得。)算法

其實只要知道len的長度,還有len和len_delta的夾角,就能夠算出len_delta的長度了。瀏覽器

1. len的長度。注意到它是等腰三角形的底邊,頂角爲angel, 容易獲得len=2*R*sin(angel/2)ide

2. len和len_delta的夾角。先能夠計算出angel_alpha,也就是等腰三角形的底角 angel_alpha = (PI - angel) / 2工具

    而後是R和原圖像的底邊的夾角angel_delta,顯然其tan值是原圖片的高寬比(注意計算增長的高度時是寬高比)。用arctan求出其角度。測試

    len和len_delta的夾角 = PI - angel_alpha - angel_delta網站

3. len_delta = len * cos(len和len_delta的夾角)spa

import java.awt.Dimension;  
import java.awt.Graphics2D;  
import java.awt.Image;  
import java.awt.Rectangle;  
import java.awt.image.BufferedImage;  
  
public class RotateImage {  
  
    public static BufferedImage Rotate(Image src, int angel) {  
        int src_width = src.getWidth(null);  
        int src_height = src.getHeight(null);  
        // calculate the new image size  
        Rectangle rect_des = CalcRotatedSize(new Rectangle(new Dimension(  
                src_width, src_height)), angel);  
  
        BufferedImage res = null;  
        res = new BufferedImage(rect_des.width, rect_des.height,  
                BufferedImage.TYPE_INT_RGB);  
        Graphics2D g2 = res.createGraphics();  
        // transform  
        g2.translate((rect_des.width - src_width) / 2,  
                (rect_des.height - src_height) / 2);  
        g2.rotate(Math.toRadians(angel), src_width / 2, src_height / 2);  
  
        g2.drawImage(src, null, null);  
        return res;  
    }  
  
    public static Rectangle CalcRotatedSize(Rectangle src, int angel) {  
        // if angel is greater than 90 degree, we need to do some conversion  
        if (angel >= 90) {  
            if(angel / 90 % 2 == 1){  
                int temp = src.height;  
                src.height = src.width;  
                src.width = temp;  
            }  
            angel = angel % 90;  
        }  
  
        double r = Math.sqrt(src.height * src.height + src.width * src.width) / 2;  
        double len = 2 * Math.sin(Math.toRadians(angel) / 2) * r;  
        double angel_alpha = (Math.PI - Math.toRadians(angel)) / 2;  
        double angel_dalta_width = Math.atan((double) src.height / src.width);  
        double angel_dalta_height = Math.atan((double) src.width / src.height);  
  
        int len_dalta_width = (int) (len * Math.cos(Math.PI - angel_alpha  
                - angel_dalta_width));  
        int len_dalta_height = (int) (len * Math.cos(Math.PI - angel_alpha  
                - angel_dalta_height));  
        int des_width = src.width + len_dalta_width * 2;  
        int des_height = src.height + len_dalta_height * 2;  
        return new java.awt.Rectangle(new Dimension(des_width, des_height));  
    }  
}
import java.io.File;  
import java.io.IOException;  
  
import javax.imageio.ImageIO;  
  
import junit.framework.Assert;  
  
import org.junit.Test;  
  
import Jugnoo.RotateImage;  
  
public class RotateImageTest {  
  
    @Test  
    public void testRotate() throws IOException {  
  
        BufferedImage src = ImageIO.read(new File("d:/dog.jpg"));  
        BufferedImage des = RotateImage.Rotate(src, 30);  
        Assert.assertNotNull(des);  
        Assert.assertTrue(ImageIO.write(des, "jpg", new File("d:/dog2.jpg")));  
  
        // bigger angel  
        des = RotateImage.Rotate(src, 150);  
        Assert.assertNotNull(des);  
        Assert.assertTrue(ImageIO.write(des, "jpg", new File("d:/dog3.jpg")));  
  
        // bigger angel  
        des = RotateImage.Rotate(src, 270);  
        Assert.assertNotNull(des);  
        Assert.assertTrue(ImageIO.write(des, "jpg", new File("d:/dog4.jpg")));  
  
    }  
  
}


說明:記得在上傳圖片的時候必定要將圖片的metaData屬性帶上。也就是圖片必定要保留其header信息,不然服務端是沒法獲取orientation的。


根據拍攝方向來判斷:


首先介紹一下什麼是EXIF,EXIF是 Exchangeable Image File的縮寫,這是一種專門爲數碼相機照片設定的格式。這種格式能夠用來記錄數字照片的屬性信息,例如相機的品牌及型號、相片的拍攝時間、拍攝時所設置的光圈大小、快門速度、ISO等等信息。除此以外它還可以記錄拍攝數據,以及照片格式化方式,這樣就能夠輸出到兼容EXIF格式的外設上,例如照片打印機等。

目前最多見的支持EXIF信息的圖片格式是JPG,不少的圖像工具均可以直接顯示圖片的EXIF信息,包括如今的一些著名的相冊網站也提供頁面用於顯示照片的EXIF信息。本文主要介紹Java語言如何讀取圖像的EXIF信息,包括如何根據EXIF信息對圖像進行調整以適合用戶瀏覽。

目前最簡單易用的EXIF信息處理的Java包是Drew Noakes寫的metadata-extractor,該項目最新的版本是2.3.0,支持EXIF 2.2版本。你能夠直接從http://www.drewnoakes.com/code/exif/ 下載該項目的最新版本包括其源碼。

須要注意的是,並非每一個JPG圖像文件都包含有EXIF信息,你能夠在Windows資源管理器單擊選中圖片後,若是該圖片包含EXIF信息,則在窗口狀態欄會顯示出相機的型號,以下圖所示:

拍攝設備的型號即是EXIF信息中的其中一個。下面咱們給出一段代碼將這個圖片的全部的EXIF信息所有打印出來。

package com.liusoft.dlog4j.test;  
   
import java.io.File;  
import java.util.Iterator;  
   
import com.drew.imaging.jpeg.JpegMetadataReader;  
import com.drew.metadata.Directory;  
import com.drew.metadata.Metadata;  
import com.drew.metadata.Tag;  
import com.drew.metadata.exif.ExifDirectory;  
   
/** 
 * 測試用於讀取圖片的EXIF信息 
 * @author Winter Lau 
 */  
public class ExifTester {  
     public static void main(String[] args) throws Exception {  
         File jpegFile = new File("D:\\個人文檔\\個人相冊\\DSCF1749.JPG");  
         Metadata metadata = JpegMetadataReader.readMetadata(jpegFile);  
         Directory exif = metadata.getDirectory(ExifDirectory.class);  
         Iterator tags = exif.getTagIterator();  
         while (tags.hasNext()) {  
             Tag tag = (Tag)tags.next();  
             System.out.println(tag);  
         }  
     }  
}

把metadata-extractor-2.3.0.jar文件加入到類路徑中編譯並執行上面這段代碼後可獲得下面的運行結果:

[Exif] Make - FUJIFILM  
[Exif] Model - FinePix A205S  
[Exif] Orientation - Top, left side (Horizontal / normal)  
[Exif] X Resolution - 72 dots per inch  
[Exif] Y Resolution - 72 dots per inch  
[Exif] Resolution Unit - Inch  
[Exif] Software - Digital Camera FinePix A205S  Ver1.00  
[Exif] Date/Time - 2005:05:13 22:18:49  
[Exif] YCbCr Positioning - Datum point  
[Exif] Copyright -      
[Exif] Exposure Time - 1/60 sec  
[Exif] F-Number - F3  
[Exif] Exposure Program - Program normal  
[Exif] ISO Speed Ratings - 320  
[Exif] Exif Version - 2.20  
[Exif] Date/Time Original - 2005:05:13 22:18:49  
[Exif] Date/Time Digitized - 2005:05:13 22:18:49  
[Exif] Components Configuration - YCbCr  
[Exif] Compressed Bits Per Pixel - 3 bits/pixel  
[Exif] Shutter Speed Value - 1/63 sec  
[Exif] Aperture Value - F3  
[Exif] Brightness Value - -61/100  
[Exif] Exposure Bias Value - 0 EV  
[Exif] Max Aperture Value - F3  
[Exif] Metering Mode - Multi-segment  
[Exif] Light Source - Unknown  
[Exif] Flash - Flash fired, auto  
[Exif] Focal Length - 5.5 mm  
[Exif] FlashPix Version - 1.00  
[Exif] Color Space - sRGB  
[Exif] Exif Image Width - 1280 pixels  
[Exif] Exif Image Height - 960 pixels  
[Exif] Focal Plane X Resolution - 1/2415 cm  
[Exif] Focal Plane Y Resolution - 1/2415 cm  
[Exif] Focal Plane Resolution Unit - cm  
[Exif] Sensing Method - One-chip color area sensor  
[Exif] File Source - Digital Still Camera (DSC)  
[Exif] Scene Type - Directly photographed image  
[Exif] Custom Rendered - Normal process  
[Exif] Exposure Mode - Auto exposure  
[Exif] White Balance - Auto white balance  
[Exif] Scene Capture Type - Standard  
[Exif] Sharpness - None  
[Exif] Subject Distance Range - Unknown  
[Exif] Compression - JPEG (old-style)  
[Exif] Thumbnail Offset - 1252 bytes  
[Exif] Thumbnail Length - 7647 bytes  
[Exif] Thumbnail Data - [7647 bytes of thumbnail data]


從這個執行的結果咱們能夠看出該照片是在2005年05月13日 22時18分49秒拍攝的,拍攝用的相機型號是富士的FinePix A205S,曝光時間是1/60秒,光圈值F3,焦距5.5毫米,ISO值爲320等等。

你也能夠直接指定讀取其中任意參數的值,ExifDirectory類中定義了不少以TAG_開頭的整數常量,這些常量表明特定的一個參數值,例如咱們要讀取相機的型號,咱們能夠用下面代碼來獲取。

Metadata metadata = JpegMetadataReader.readMetadata(jpegFile);  
Directory exif = metadata.getDirectory(ExifDirectory.class);  
String model = exif.getString(ExifDirectory.TAG_MODEL);


上述提到的是如何獲取照片的EXIF信息,其中包含一個很重要的信息就是——拍攝方向。例如上面例子所用的圖片的拍攝方向是:Orientation - Top, left side (Horizontal / normal)。咱們在拍照的時候常常會根據場景的不一樣來選擇相機的方向,例如拍攝一顆高樹,咱們會把相機豎着拍攝,使景物恰好適合整個取景框,可是這樣獲得的圖片若是用普通的圖片瀏覽器看即是倒着的,須要調整角度才能獲得一個正常的圖像,有以下面一張照片。

這張圖片正常的狀況下須要向左調整90度,也就是順時針旋轉270度才適合觀看。經過讀取該圖片的EXIF信息,咱們獲得關於拍攝方向的這樣一個結果:[Exif] Orientation - Left side, bottom (Rotate 270 CW)。而直接讀取ExitDirectory.TAG_ORIENTATION標籤的值是8。咱們再來看這個項目是如何來定義這些返回值的,打開源碼包中的ExifDescriptor類的getOrientationDescription方法,該方法代碼以下:

public String getOrientationDescription() throws MetadataException  
{  
        if (!_directory.containsTag(ExifDirectory.TAG_ORIENTATION)) return null;  
        int orientation = _directory.getInt(ExifDirectory.TAG_ORIENTATION);  
        switch (orientation) {  
            case 1: return "Top, left side (Horizontal / normal)";  
            case 2: return "Top, right side (Mirror horizontal)";  
            case 3: return "Bottom, right side (Rotate 180)";  
            case 4: return "Bottom, left side (Mirror vertical)";  
            case 5: return "Left side, top (Mirror horizontal and rotate 270 CW)";  
            case 6: return "Right side, top (Rotate 90 CW)";  
            case 7: return "Right side, bottom (Mirror horizontal and rotate 90 CW)";  
            case 8: return "Left side, bottom (Rotate 270 CW)";  
            default:  
                return String.valueOf(orientation);  
        }  
}

從這個方法咱們能夠清楚看到各個返回值的意思,如此咱們即可以根據實際的返回值來對圖像進行旋轉或者是鏡像處理了。在這個例子中咱們須要將圖片順時針旋轉270度,或者是逆時針旋轉90度方可獲得正常的圖片。

雖然圖片的旋轉不在本文範疇內,但爲了有始有終,下面給出代碼用以旋轉圖片,其餘的關於圖片的鏡像等處理讀者能夠依此類推。

String path = "D:\\TEST.JPG";  
File img = new File(path);  
BufferedImage old_img = (BufferedImage)ImageIO.read(img);    
int w = old_img.getWidth();  
int h = old_img.getHeight();  
   
BufferedImage new_img = new BufferedImage(h,w,BufferedImage.TYPE_INT_BGR);        
Graphics2D g2d =new_img.createGraphics();  
        
AffineTransform origXform = g2d.getTransform();  
AffineTransform newXform = (AffineTransform)(origXform.clone());  
// center of rotation is center of the panel  
double xRot = w/2.0;  
newXform.rotate(Math.toRadians(270.0), xRot, xRot); //旋轉270度  
   
g2d.setTransform(newXform);   
// draw image centered in panel  
g2d.drawImage(old_img, 0, 0, null);  
// Reset to Original  
g2d.setTransform(origXform);  
//寫到新的文件  
FileOutputStream out = new FileOutputStream("D:\\test2.jpg");  
try{  
ImageIO.write(new_img, "JPG", out);  
}finally{  
    out.close();  
}


旋轉後的照片以下:

可是利用上面的代碼旋轉照片後,原有照片包含的EXIF信息不復存在了。至於照片的鏡面翻轉能夠直接利用Graphic2D的drawImage方法來實現,方法原形以下:
drawImage

public abstract boolean drawImage(Image img,
                   int dx1,
                                  int dy1,
                                  int dx2,
                                  int dy2,
                                  int sx1,
                                  int sy1,
                                  int sx2,
                                  int sy2,
                                  ImageObserver observer)
該方法的使用請參考JDK的API文檔。關於照片旋轉後丟失EXIF信息的問題,須要在照片旋轉以前先把EXIF信息讀出,而後再在旋轉後寫入新的照片中,你可使用MediaUtil包來寫EXIF信息到圖片文件中,關於這個包的使用請參考該項目所給出的例子,本文再也不敘述。

Exif的Orientation信息說明

EXIF Orientation 參數讓你隨便照像但均可以看到正確方向的照片而無需手動旋轉(前提要圖片瀏覽器支持,Windows 自帶的不支持)

這個參數在佳能、尼康相機照的照片是自帶的,但個人奧林巴斯就沒有,看照片時不能自動旋轉,修正的方法有兩個,一個看不順眼就旋轉,另外一個是修改 EXIF 中的 Orientation 參數(XnView 瀏覽器查看縮略圖時能夠修改)

若是你想在旋轉圖片時只寫入 EXIF 方向信息而不旋轉圖片就能夠用到下面的方法

2,4,5,7功能相似 Photoshop 的水平翻轉、垂直翻轉,照像時不會出現的,自拍也不會(對着鏡子自拍能夠,但相機不知道)

讀取方法:未旋轉的照片讀上左旋轉後的方向對照下表。至關於把照片當相機,看旋轉後相機上方和左方分別對着什麼方向
拿名片或相機來轉一下最好理解

參數含義:

照像者面對相機(非被照像的人,便是未旋轉照片)上邊爲0行,左邊爲0列
上下左右指旋轉後正確方向照片的四個方向

看下面兩張圖片就比較簡單了

EXIF Orientation Flag Values

EXIF Orientation Flag Values

EXIF Orientation Flag Values

EXIF Orientation Flag Values

EXIF 2.2 官方標準:http://www.exif.org/Exif2-2.PDF

來自於http://www.impulseadventure.com/photo/exif-orientation.html

相關文章
相關標籤/搜索