为什么图片会旋转,旋转角是什么

目前从事主营业务为在线设计的一家公司,其中某个业务为用户在线设计,然后将用户创建的设计下载渲染成图片。前几天,我在工作中遇到了一个奇怪的问题。某张用苹果手机拍摄的图片在浏览器中使用时是正着的,但是当下载生成图片后图片便横了过来。同样的照片,我使用小米手机拍摄则可以正常使用,很好奇这是为什么,因为这确实影响到了我的正常使用,于是研究了一下其中的原因。

 
在浏览器中使用时
生成图片时

 

带着上边的问题,我们先来了解一下关于图片的一些数据信息存储方式,以及关于图片的一些知识。

一、什么是Exif

Exif(可交换图像文件格式)是一种协议,用于存储有关数码相机拍摄的图像的各种元信息。Exif与实际图像数据一起存储。Exif中的某些元信息包括相机制造商,快门速度,焦距,方向,拍摄时间等。这些元信息称为标签,每个标签都有一个由Exif格式标准决定的特定标签号。标签的完整列表及其相关信息可在此处找到。

二、Exif怎么控制图片方向

在这里,我们对方向元信息感兴趣。用相机拍摄照片时,可能并不总是将相机保持在相机顶部与场景顶部相对应的位置。该博客下面的图片 清楚地说明了这个想法:

但是,无论如何握持相机,如果您在计算机上查看图像,图像都将以正确的方向显示。这与Exif方向标志有关。当您以非直立姿势握持照相机时,所拍摄的原始照片将存储为旋转的图像。数字设备(例如智能手机或数码相机)具有传感器,用于记录相机的方向并将该信息写入Exif中的方向标志。

Exif方向标记可以具有1到9的9个不同值。下图2显示了其中的八个:

通常,对于数码照片,您只会得到标记1、8、3、6。标志2、7、4、5代表镜像和旋转的图像版本。

三、为什么会出现图片旋转的情况?

因为苹果手机拍摄的照片会带有90°的旋转角,小米手机则不会(有的安卓也会,比如三星),当我们在苹果手机上或者是浏览器上查看带有exif属性的图片时,因为两者都读取了Exif信息并根据信息对图片进行了适应,它将基于方向信息自动旋转原始图像,所以我们看到的就是我们拍摄的。但是当我们使用没有兼容图片的exif信息的软件读取或者查看时,则看到的是旋转之前的图片,所以给我们的感觉看到的和拍摄的方向颠倒了。

同理,我们的图片渲染引擎不支持读取图片的这种额外信息,所以它处理的实际是图片的原图,即旋转之前的图片,所以浏览器是支持的展示没有问题,但是下载下来就有问题了。

四、如何读取Exif信息

这里推荐两款软件,自认为比较好用的

JPEGsnoop

官网地址:https://www.impulseadventure.com/photo/jpeg-snoop.html

每张数码照片都包含大量隐藏的信息-JPEGsnoop的编写是为了向好奇的人公开这些细节。该软件还可以判断图片是否进行过ps(不要叫女朋友知道)。

直接导入图片就可以,图片读取完之后会自动将图片的属性输出到日志中,从上图红框里可以看到,该图片带有旋转属性,并且进行了90°的旋转,可以看到Orientation的值为6,可以在Exif方向判断哪里找到6对应的旋转方向。

IrfanView

官网地址:https://www.irfanview.com

IrfanView是Windows上出色的图像查看器,它也可以查看图像的Exif信息。

默认情况下,IrfanView会遵守Exif信息,并将根据其方向标记自动旋转图像。要禁用此行为,请转到Options -> Properties/Settings,单击JPG/PCD/GIF并取消选中该框Auto-rotate image according to EXIF info (if available)

Java读取图片Exif信息

我使用的是metadata-extractor,开源,很多“厂子”都在用。

项目地址:https://github.com/drewnoakes/metadata-extractor

maven依赖

<dependency>
  <groupId>com.drewnoakes</groupId>
  <artifactId>metadata-extractor</artifactId>
  <version>2.14.0</version>
</dependency>

示例代码

    @Test
    public void getImageInfo(){
        try {
            // 获取图片元信息
            Metadata metadata = ImageMetadataReader.readMetadata(new File("C:\\Users\\Administrator\\Desktop\\image.jpg"));
            // 获取图片标签库
            Iterable<Directory> directories = metadata.getDirectories();
            for(Directory directory : directories){
                // 获取图片标签信息
                Collection<Tag> tags = directory.getTags();
                for(Tag tag : tags){
                    System.out.println(tag.toString());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

输出信息

[File] File Name - image.jpg
[File] File Size - 2188218 bytes
[File] File Modified Date - Mon Jun 29 10:28:57 CST 2020
[ICC Profile] Profile Size - 548
[ICC Profile] CMM Type - appl
[ICC Profile] Version - 4.0.0
[ICC Profile] Class - Display Device
[ICC Profile] Color space - RGB 
[ICC Profile] Profile Connection Space - XYZ 
[ICC Profile] Profile Date/Time - Mon Aug 07 21:22:32 CST 2017
[ICC Profile] Signature - acsp
[ICC Profile] Primary Platform - Apple Computer, Inc.
[ICC Profile] Device manufacturer - APPL
[ICC Profile] XYZ values - 0.9642029 1.0 0.8249054
[ICC Profile] Tag Count - 10
[ICC Profile] Profile Description - Display P3
[ICC Profile] Copyright - Copyright Apple Inc., 2017
[ICC Profile] Media White Point - (0.9504547, 1.0, 1.0890503)
[ICC Profile] Red Colorant - (0.51512146, 0.24119568, 65536.0)
[ICC Profile] Green Colorant - (0.29197693, 0.6922455, 0.041885376)
[ICC Profile] Blue Colorant - (0.15710449, 0.0665741, 0.7840729)
[ICC Profile] Red TRC - para(0x70617261): 32 bytes
[ICC Profile] Chromatic Adaptation - sf32(0x73663332): 44 bytes
[ICC Profile] Blue TRC - para(0x70617261): 32 bytes
[ICC Profile] Green TRC - para(0x70617261): 32 bytes
[Exif IFD0] Orientation - Right side, top (Rotate 90 CW)
[Exif IFD0] X Resolution - 72 dots per inch
[Exif IFD0] Y Resolution - 72 dots per inch
[Exif IFD0] Resolution Unit - Inch
[Exif IFD0] YCbCr Positioning - Center of pixel array
[Exif SubIFD] Exif Version - 2.21
[Exif SubIFD] Components Configuration - YCbCr
[Exif SubIFD] FlashPix Version - 1.00
[Exif SubIFD] Color Space - sRGB
[Exif SubIFD] Exif Image Width - 4032 pixels
[Exif SubIFD] Exif Image Height - 3024 pixels
[Exif SubIFD] Scene Capture Type - Standard
[JPEG] Compression Type - Baseline
[JPEG] Data Precision - 8 bits
[JPEG] Image Height - 3024 pixels
[JPEG] Image Width - 4032 pixels
[JPEG] Number of Components - 3
[JPEG] Component 1 - Y component: Quantization table 0, Sampling factors 2 horiz/2 vert
[JPEG] Component 2 - Cb component: Quantization table 1, Sampling factors 1 horiz/1 vert
[JPEG] Component 3 - Cr component: Quantization table 1, Sampling factors 1 horiz/1 vert
[Exif Thumbnail] Thumbnail Compression - JPEG (old-style)
[Exif Thumbnail] X Resolution - 72 dots per inch
[Exif Thumbnail] Y Resolution - 72 dots per inch
[Exif Thumbnail] Resolution Unit - Inch
[Exif Thumbnail] Thumbnail Offset - 286 bytes
[Exif Thumbnail] Thumbnail Length - 9057 bytes

属性解释

五、怎么解决图片旋转的问题

1、将图片按照旋转角旋转的值再将图片转回去,应用到我们的业务中,就是保证用户在设计中图片的方向保持和下载一致,也就是说叫浏览器也不读取图片的EXIF数据,然后在通过我们设计工具自带的旋转功能手动将图片旋转回去,这样的问题就是对用户不够友好,但是保证了用户做的和渲染出来的一致。

通过Java代码将图片旋转回去,在用户上传图片时通过代码将图片旋转,但是这样实际保存的就不是用户上传的原始图片,而是处理之后的图片。

参考:https://blog.csdn.net/c20081052/article/details/89479970?utm_medium=distribute.pc_relevant_t0.none-task-blog-OPENSEARCH-1.compare&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-OPENSEARCH-1.compare

public class ImageTest {

    @Test
    public void test(){
        String fileFromPath = "C:\\Users\\Administrator\\Desktop\\image.jpg";
        String fileToPath = "C:\\Users\\Administrator\\Desktop\\target_image.jpg";
        String contentType = "jpg";

        try {
            rotate(fileFromPath,fileToPath, contentType);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * @desc 旋转图片
     * @author dataozi
     * @date 2020/7/1 9:20
     * @param fileFromPath 源图片路径
     * @param fileToPath 目标图片路径
     * @param contentType 图片类型
     */
    private void rotate(String fileFromPath,String fileToPath, String contentType) throws Exception{
        // 校验参数
        if(StringUtils.isBlank(fileFromPath)){
            throw new RuntimeException("file path can not be null");
        }

        File image = new File(fileFromPath);
        if(!image.exists()){
            throw new RuntimeException(String.format("%s can not be find", fileFromPath));
        }

        contentType = StringUtils.isBlank(contentType) ? "jpg" : contentType;

        // 获取图片旋转角度
        Integer angel = getImageRotateAngle(image);
        if(NumberUtils.INTEGER_ZERO.equals(angel)){
            return;
        }

        // 读取原图片的宽高
        BufferedImage bufferedImage = ImageIO.read(image);
        int width = bufferedImage.getWidth(null);
        int height = bufferedImage.getHeight(null);

        // 计算目标图片的宽高
        int[] mathNewSize = mathNewSize(width, height, angel);
        int targetWidth = mathNewSize[0];
        int targetHeight = mathNewSize[1];

        // 绘制目标图片
        BufferedImage res = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2 = res.createGraphics();
        g2.translate((targetWidth - width) / 2, (targetHeight - height) / 2);
        g2.rotate(Math.toRadians(angel), width / 2.0, height / 2.0);
        g2.drawImage(bufferedImage, null, null);

        // 输出目标图片
        ImageIO.write(res,contentType, new File(fileToPath));
    }

    /**
     * @desc 获取旋转之后图片的宽高
     * @author dataozi
     * @date 2020/7/1 9:45
     * @param width 原图宽
     * @param height 原图高
     * @param angel 旋转角度
     * @return int[]  arr[0]目标图片宽  arr[1]目标图片高
     */
    private int[] mathNewSize(int width, int height, Integer angel) {
        if (angel >= 90) {
            if (angel / 90 % 2 == 1) {
                int temp = height;
                height = width;
                width = temp;
            }
            angel = angel % 90;
        }

        // 求平方根
        double r = Math.sqrt(height * height + width * width) / 2;
        double len = 2 * Math.sin(Math.toRadians(angel) / 2) * r;
        double angelAlpha = (Math.PI - Math.toRadians(angel)) / 2;
        // 根据图片旋转中心画圆,计算目标图片宽高
        double angelWidth = Math.atan((double) height / width);
        double angelHeight = Math.atan((double) width / height);
        int lenWidth = (int) (len * Math.cos(Math.PI - angelAlpha
                - angelWidth));
        int lenHeight = (int) (len * Math.cos(Math.PI - angelAlpha
                - angelHeight));
        // 计算新的宽高
        return new int[]{width + lenWidth * 2, height + lenHeight * 2};
    }

    /**
     * @desc 获取原图片的旋转角
     * @author dataozi
     * @date 2020/7/1 8:44
     * @param image 图片文件
     * @return 旋转角度数
     */
    private Integer getImageRotateAngle(File image){
        Integer angle = NumberUtils.INTEGER_ZERO;
        try {
            // 获取图片元信息
            Metadata metadata = ImageMetadataReader.readMetadata(image);
            // 这里我们直接获取图片元信息标签库
            ExifIFD0Directory directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
            // 这里我们直接获取旋转角度。注意,这里返回的是方向值(1,6,8,3)
            int orientation = directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
            // 这里我们将角度值转换为旋转角度
            switch (orientation){
                case 6:
                    angle = 90;
                    break;
                case 3:
                    angle = 180;
                    break;
                case 8:
                    angle = 270;
                    break;
                default:
                    break;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return angle;
    }
}

2、通过阿里云OSS

因为我们的图片借用了阿里云的OSS,在用户上传时直接将图片上传上去,然后通过OSS的访问参数控制图片不旋转,这样保证了不会更改用户上传的原图,同时达到效果。

3、对图片进行压缩,相当于去除图片的exif属性,同时保证图片不进行回转。这样保证了用户上传的图和设计中的图一致,并且因为没有了旋转参数,下载也就一致了。

    @Test
    public void zipImage(){
        String fromImage = "C:\\Users\\Administrator\\Desktop\\image.jpg";
        String toImage = "C:\\Users\\Administrator\\Desktop\\target_image.jpg";
        String contentType = "jpg";

        try {
            BufferedImage bufferedImage = ImageIO.read(new File(fromImage));
            int width = bufferedImage.getWidth(null);
            int height = bufferedImage.getHeight(null);
            Thumbnails.of(fromImage).size(height, width).outputQuality(1f).outputFormat(contentType).toFile(toImage);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

4、叫算法部门渲染引擎兼容旋转角问题。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值