文档章节

Java实现图片内容无损任意角度旋转

IT_小翼
 IT_小翼
发布于 2016/03/10 14:15
字数 2788
阅读 1635
收藏 3

主要问题是如何在图片做旋转后计算出新图片的长宽。

在java 2d和基本math库的帮助下,其实利用简单的计算就可以知道。


以下算法只是计算出旋转小于90度时的公式。当旋转大于90时,可以先把问题域换算到锐角的情况,再进行计算即可。

如下图所示,需要计算出来的是len_delta的长度,就是有双竖线的位置,它是新图片要增加的宽。(要增加的高度同理可得。)

其实只要知道len的长度,还有len和len_delta的夹角,就可以算出len_delta的长度了。

1. len的长度。注意到它是等腰三角形的底边,顶角为angel, 容易得到len=2*R*sin(angel/2)

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的夹角)

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


本文转载自:http://blog.csdn.net/heliang7/article/details/7309394;http://blog.csdn.net/ghsau/article/details/...

IT_小翼

IT_小翼

粉丝 44
博文 153
码字总数 36364
作品 0
西安
程序员
私信 提问
android 围绕中心旋转动画

本文主要介绍Android中如何使用rotate实现图片不停旋转的效果。Android 平台提供了两类动画,一类是 Tween 动画,即通过对场景里的对象不断做图像变换(平移、缩放、旋转)产生动画效果;第二类...

程序袁_绪龙
2015/03/05
704
0
android 围绕中心旋转动画

本文主要介绍Android中如何使用rotate实现图片不停旋转的效果。Android 平台提供了两类动画,一类是 Tween 动画,即通过对场景里的对象不断做图像变换(平移、缩放、旋转)产生动画效果;第二类...

Jonson
2013/03/29
106K
8
Java base64图片编码上传,判断图片是否有旋转,修正并保存

最近遇到一个问题,手机端上传的图片为base64编码格式的,服务器需要通过解码字符串然后判断图片是否有旋转角度,在保存为图片。手上有一个.net的例子,但需要转为Java语言的,由于对Java的类...

OSC-原谅帽派送员
2016/06/20
2.1K
2
Android Tween动画之RotateAnimation实现图片不停旋转

本文主要介绍Android中如何使用rotate实现图片不停旋转的效果。Android 平台提供了两类动画,一类是 Tween 动画,即通过对场景里的对象不断做图像变换(平移、缩放、旋转)产生动画效果;第二类...

崔同亮
2013/09/26
13.5K
2
ImageJ感触

以下介绍是摘自维基百科: --------------------------------------- ImageJ是一个基于java的公共的图像处理软件,它是由National Institutes of Health开发的。可运行于Microsoft Windows,M...

修真0
2012/03/09
2.2K
3

没有更多内容

加载失败,请刷新页面

加载更多

64.监控平台介绍 安装zabbix 忘记admin密码

19.1 Linux监控平台介绍 19.2 zabbix监控介绍 19.3/19.4/19.6 安装zabbix 19.5 忘记Admin密码如何做 19.1 Linux监控平台介绍: 常见开源监控软件 ~1.cacti、nagios、zabbix、smokeping、ope...

oschina130111
今天
13
0
当餐饮遇上大数据,嗯真香!

之前去开了一场会,主题是「餐饮领袖新零售峰会」。认真听完了餐饮前辈和新秀们的分享,觉得获益匪浅,把脑子里的核心纪要整理了一下,今天和大家做一个简单的分享,欢迎感兴趣的小伙伴一起交...

数澜科技
今天
7
0
DNS-over-HTTPS 的下一代是 DNS ON BLOCKCHAIN

本文作者:PETER LAI ,是 Diode 的区块链工程师。在进入软件开发领域之前,他主要是在做工商管理相关工作。Peter Lai 也是一位活跃的开源贡献者。目前,他正在与 Diode 团队一起开发基于区块...

红薯
今天
10
0
CC攻击带来的危害我们该如何防御?

随着网络的发展带给我们很多的便利,但是同时也带给我们一些网站安全问题,网络攻击就是常见的网站安全问题。其中作为站长最常见的就是CC攻击,CC攻击是网络攻击方式的一种,是一种比较常见的...

云漫网络Ruan
今天
12
0
实验分析性专业硕士提纲撰写要点

为什么您需要研究论文的提纲? 首先当您进行研究时,您需要聚集许多信息和想法,研究论文提纲可以较好地组织你的想法, 了解您研究资料的流畅度和程度。确保你写作时不会错过任何重要资料以此...

论文辅导员
今天
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部