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

在浏览器中使用时

生成图片时

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

一、什么是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>

示例代码

    @Testpublic 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 {@Testpublic 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属性,同时保证图片不进行回转。这样保证了用户上传的图和设计中的图一致,并且因为没有了旋转参数,下载也就一致了。

    @Testpublic 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、叫算法部门渲染引擎兼容旋转角问题。

为什么图片会旋转,旋转角是什么相关推荐

  1. python图片旋转脚本_Python+OpenCV 实现图片无损旋转90°且无黑边

    0. 引言 有如上一张图片,在以往的图像旋转处理中,往往得到如图所示的图片. 然而,在进行一些其他图像处理或者图像展示时,黑边带来了一些不便.本文解决图片旋转后出现黑边的问题,实现了图片尺寸不变的旋转 ...

  2. Android开发--Matrix(二)--实现图片的旋转

    Matrix功能很是强大,利用这个类提供的一系列方法,我们可以实现图片的旋转. 下面以一个例子说明实现方法. 首先,我们看下实现的截图: 下面给出具体的实现代码: 1.xml布局文件 <?xml ...

  3. java旋转图片并画出_java实现图片角度旋转并获得图片信息

    本文实例为大家分享了java实现图片角度旋转并获得图片信息的具体代码,供大家参考,具体内容如下 public class demo { /** * 调整图片角度 * make by dongxh 20 ...

  4. UWP 图片剪切旋转工具

    原文:UWP 图片剪切旋转工具 好久没撸随笔了,明天终于放假休息了..准备去进行信仰充值,看<魔兽>去(话说surface phone 好久出,让我这个做UWP的也充点信仰..) 先上下效 ...

  5. ios 拍照上传到服务器_ios端浏览器拍照上传到服务器,图片被旋转90度 php 解决方案...

    1.可以通过前端进行解决,本案例通过后端解决的 判断请求的浏览器的ua,如果是ios浏览器则进行90度旋转 重点来了: 必须确保检测的图片是ios设备上传的完整图片,不要在前端压缩过的,因为压缩后的图 ...

  6. 网页特效:用CSS3制作3D图片立方体旋转特效

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...

  7. java根据exif旋转,关于图片文件旋转JPEG与EXIF信息

    关于图片文件旋转JPEG与EXIF信息 2019/10/31 0:36:39  YuLimin  程序员俱乐部  我要评论(1) 摘要:关于图片文件旋转JPEG与EXIF信息比如某相机拍摄出来的相片, ...

  8. C#中图片单击旋转事件

    #region 图片单击旋转事件         private void pb_Heads_Click(object sender, EventArgs e)         {           ...

  9. Android 拍照后图片的旋转,合并,兼容性 相机开发

    在看这篇文章之前,我建议先看相机开发基础 针对这个功能需要做自定义相机,根据Camera相机类和SurfaceView类来实现自定义图形预览拍照功能. 但在实现过程中出现几个难点: 1.如何将自己产品 ...

最新文章

  1. stdio.h头文件中申明的基本函数
  2. 60款与DevOps相关的开源工具
  3. Android接口和框架学习
  4. Paper:《Adam: A Method for Stochastic Optimization》的翻译与解读
  5. NDK-r14b + FFmpeg-release-3.4 linux下编译FFmpeg
  6. 微信无法连接服务器1-502,只有一部iphone x手机,在微信公众号中选择菜单,出现bad gateway 502错误,原因?...
  7. 计算机win10启动慢,Win10 开机慢/Win10启动慢的常见原因
  8. Android 数据库 哪个好,目前最好用的安卓数据库,DBFlow使用详解
  9. python与机械教育初探_Python公开课-机械学习之手写识别
  10. 重磅福利!程序员面试——算法工程师面试大全第六部分
  11. 量子计算中几种常见量子比特介质研究
  12. Centos7之LVM(逻辑卷管理器)
  13. 从入门到放弃:微信小程序入门个人指南Day 4
  14. 运维监控软件的选择对比----Zabbix vs Prometheus
  15. 网页中滑动导航菜单制作
  16. 一加8T,一加8和一加8Pro有什么区别哪个好?分析优缺点?
  17. wpscan扫描的简单介绍(对WordPress的扫描CMS)
  18. 6 款代码对比工具,你知道几个?
  19. UnityRectTranform属性设置方法
  20. HashMap 源码深度分析

热门文章

  1. litepoint python SCPI通信
  2. 博客导航置顶快速直达滴滴滴
  3. DevExpress ChartControl ToolTipPointPattern和ToolTipSeriesPattern
  4. 2015蓝桥杯python——三羊献瑞
  5. 2022-07-04 共享文件samba不能访问linux软连接的问题
  6. Hadoop学习之路(二)Hadoop发展背景
  7. 华为怎么分屏操作技巧_第五人格勘探员怎么操作 求生者勘探员操作技巧介绍...
  8. 苹果App store app上架转让注意事项,无续费按钮,无转让按钮,给人工打电话
  9. 工厂模式 java好处_java中工厂模式的优缺点有哪些
  10. 华为Mate 20 X开启5G手机新时代