Android进阶之路 - 解决部分手机拍照之后图片被旋转的问题
這幾天犯了一個(gè)錯(cuò)誤,初期想著甩鍋給后臺(tái)的… 但還好及時(shí)發(fā)現(xiàn)了是自身的問題~
關(guān)聯(lián)文章
- Android基礎(chǔ)進(jìn)階 - 調(diào)用拍照、獲取圖片(基礎(chǔ))
- Android基礎(chǔ)進(jìn)階 - 獲取、調(diào)用相冊(cè)內(nèi)圖片(基礎(chǔ))
- Android進(jìn)階之路 - 拍照、相冊(cè)選圖
- Android進(jìn)階之路 - 解決部分手機(jī)拍照之后圖片被旋轉(zhuǎn)的問題
- Android進(jìn)階之路 - Uri、Path、File、Bitmap 轉(zhuǎn)換方式
Hint:底部擴(kuò)展了騰訊某個(gè)項(xiàng)目內(nèi)用到的ImageUtil,可以很好的解決圖片旋轉(zhuǎn)、壓縮、保存的問題
- 拍照、相冊(cè)、圖片回傳
- 野生 PhotoBitmapUtils
- 騰訊 ImageUtil
整篇思考
-
產(chǎn)生問題:通過拍照或相冊(cè)回傳的照片,直接傳入后臺(tái),在用于展示的時(shí)候,照片角度出現(xiàn)問題,均不規(guī)則的旋轉(zhuǎn)了90度,從而導(dǎo)致體驗(yàn)效果變差!
-
問題思考:后臺(tái)一般都是你怎么存的,它怎么給你拿出來!所以在這里就可以發(fā)現(xiàn)問題本身是由我們前端造成的!
-
覆蓋范圍:圖片被旋轉(zhuǎn)的情況并不是百分百出現(xiàn)在每個(gè)機(jī)型上,只是會(huì)出現(xiàn)在某一部分機(jī)型之上,但是本著兼容的原則,我們只有逐個(gè)處理
-
解決方法:找了大約五篇博文左右,我找到了最懶也是最簡單有效的方法,主要來自此篇文章,之所以沒有轉(zhuǎn)載而且署名原創(chuàng)的原因是因?yàn)楦嬗涗涀约河龅降膯栴}、場景、思路~
拍照、相冊(cè)、圖片回傳
主要代碼部分,已全局進(jìn)行了注釋 (os:我想不會(huì)有比我還笨的人,加油把~)
- 拍照
- 相冊(cè)選取
- 重寫onActivityResult(此時(shí)照片已經(jīng)回傳,我們?cè)谶@里進(jìn)行圖片處理)
野生 PhotoBitmapUtils
(主要工具類):
/*** 集合一些圖片工具** Created by zhuwentao on 2016-07-22.*/ public class PhotoBitmapUtils {/*** 存放拍攝圖片的文件夾*/private static final String FILES_NAME = "/MyPhoto";/*** 獲取的時(shí)間格式*/public static final String TIME_STYLE = "yyyyMMddHHmmss";/*** 圖片種類*/public static final String IMAGE_TYPE = ".png";// 防止實(shí)例化private PhotoBitmapUtils() {}/*** 獲取手機(jī)可存儲(chǔ)路徑** @param context 上下文* @return 手機(jī)可存儲(chǔ)路徑*/private static String getPhoneRootPath(Context context) {// 是否有SD卡if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)|| !Environment.isExternalStorageRemovable()) {// 獲取SD卡根目錄return context.getExternalCacheDir().getPath();} else {// 獲取apk包下的緩存路徑return context.getCacheDir().getPath();}}/*** 使用當(dāng)前系統(tǒng)時(shí)間作為上傳圖片的名稱** @return 存儲(chǔ)的根路徑+圖片名稱*/public static String getPhotoFileName(Context context) {File file = new File(getPhoneRootPath(context) + FILES_NAME);// 判斷文件是否已經(jīng)存在,不存在則創(chuàng)建if (!file.exists()) {file.mkdirs();}// 設(shè)置圖片文件名稱SimpleDateFormat format = new SimpleDateFormat(TIME_STYLE, Locale.getDefault());Date date = new Date(System.currentTimeMillis());String time = format.format(date);String photoName = "/" + time + IMAGE_TYPE;return file + photoName;}/*** 保存Bitmap圖片在SD卡中* 如果沒有SD卡則存在手機(jī)中** @param mbitmap 需要保存的Bitmap圖片* @return 保存成功時(shí)返回圖片的路徑,失敗時(shí)返回null*/public static String savePhotoToSD(Bitmap mbitmap, Context context) {FileOutputStream outStream = null;String fileName = getPhotoFileName(context);try {outStream = new FileOutputStream(fileName);// 把數(shù)據(jù)寫入文件,100表示不壓縮mbitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream);return fileName;} catch (Exception e) {e.printStackTrace();return null;} finally {try {if (outStream != null) {// 記得要關(guān)閉流!outStream.close();}if (mbitmap != null) {mbitmap.recycle();}} catch (Exception e) {e.printStackTrace();}}}/*** 把原圖按1/10的比例壓縮** @param path 原圖的路徑* @return 壓縮后的圖片*/public static Bitmap getCompressPhoto(String path) {BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = false;options.inSampleSize = 10; // 圖片的大小設(shè)置為原來的十分之一Bitmap bmp = BitmapFactory.decodeFile(path, options);options = null;return bmp;}/*** 處理旋轉(zhuǎn)后的圖片* @param originpath 原圖路徑* @param context 上下文* @return 返回修復(fù)完畢后的圖片路徑*/public static String amendRotatePhoto(String originpath, Context context) {// 取得圖片旋轉(zhuǎn)角度int angle = readPictureDegree(originpath);// 把原圖壓縮后得到Bitmap對(duì)象Bitmap bmp = getCompressPhoto(originpath);;// 修復(fù)圖片被旋轉(zhuǎn)的角度Bitmap bitmap = rotaingImageView(angle, bmp);// 保存修復(fù)后的圖片并返回保存后的圖片路徑return savePhotoToSD(bitmap, context);}/*** 讀取照片旋轉(zhuǎn)角度** @param path 照片路徑* @return 角度*/public static int readPictureDegree(String path) {int degree = 0;try {ExifInterface exifInterface = new ExifInterface(path);int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);switch (orientation) {case ExifInterface.ORIENTATION_ROTATE_90:degree = 90;break;case ExifInterface.ORIENTATION_ROTATE_180:degree = 180;break;case ExifInterface.ORIENTATION_ROTATE_270:degree = 270;break;}} catch (IOException e) {e.printStackTrace();}return degree;}/*** 旋轉(zhuǎn)圖片* @param angle 被旋轉(zhuǎn)角度* @param bitmap 圖片對(duì)象* @return 旋轉(zhuǎn)后的圖片*/public static Bitmap rotaingImageView(int angle, Bitmap bitmap) {Bitmap returnBm = null;// 根據(jù)旋轉(zhuǎn)角度,生成旋轉(zhuǎn)矩陣Matrix matrix = new Matrix();matrix.postRotate(angle);try {// 將原始圖片按照旋轉(zhuǎn)矩陣進(jìn)行旋轉(zhuǎn),并得到新的圖片returnBm = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);} catch (OutOfMemoryError e) {}if (returnBm == null) {returnBm = bitmap;}if (bitmap != returnBm) {bitmap.recycle();}return returnBm;} }騰訊 ImageUtil
擴(kuò)展 - 騰訊部分項(xiàng)目用到的ImageUtil
ImageUtil
package com.tencent.qcloud.tuicore.util;import android.content.Context; import android.content.SharedPreferences; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; import android.media.ExifInterface; import android.net.Uri; import android.text.TextUtils; import android.util.Log;import com.tencent.qcloud.tuicore.TUIConfig; import com.tencent.qcloud.tuicore.TUILogin;import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream;public class ImageUtil {public final static String SP_IMAGE = "_conversation_group_face";/*** @param outFile 圖片的目錄路徑* @param bitmap* @return*/public static File storeBitmap(File outFile, Bitmap bitmap) {// 檢測(cè)是否達(dá)到存放文件的上限if (!outFile.exists() || outFile.isDirectory()) {outFile.getParentFile().mkdirs();}FileOutputStream fOut = null;try {outFile.deleteOnExit();outFile.createNewFile();fOut = new FileOutputStream(outFile);bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fOut);fOut.flush();} catch (IOException e1) {outFile.deleteOnExit();} finally {if (null != fOut) {try {fOut.close();} catch (IOException e) {e.printStackTrace();outFile.deleteOnExit();}}}return outFile;}public static Bitmap getBitmapFormPath(Uri uri) {Bitmap bitmap = null;try {InputStream input = TUIConfig.getAppContext().getContentResolver().openInputStream(uri);BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();onlyBoundsOptions.inJustDecodeBounds = true;onlyBoundsOptions.inDither = true;//optionalonlyBoundsOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//optionalBitmapFactory.decodeStream(input, null, onlyBoundsOptions);input.close();int originalWidth = onlyBoundsOptions.outWidth;int originalHeight = onlyBoundsOptions.outHeight;if ((originalWidth == -1) || (originalHeight == -1))return null;//圖片分辨率以480x800為標(biāo)準(zhǔn)float hh = 800f;//這里設(shè)置高度為800ffloat ww = 480f;//這里設(shè)置寬度為480fint degree = getBitmapDegree(uri);if (degree == 90 || degree == 270) {hh = 480;ww = 800;}//縮放比。由于是固定比例縮放,只用高或者寬其中一個(gè)數(shù)據(jù)進(jìn)行計(jì)算即可int be = 1;//be=1表示不縮放if (originalWidth > originalHeight && originalWidth > ww) {//如果寬度大的話根據(jù)寬度固定大小縮放be = (int) (originalWidth / ww);} else if (originalWidth < originalHeight && originalHeight > hh) {//如果高度高的話根據(jù)寬度固定大小縮放be = (int) (originalHeight / hh);}if (be <= 0)be = 1;//比例壓縮BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();bitmapOptions.inSampleSize = be;//設(shè)置縮放比例bitmapOptions.inDither = true;//optionalbitmapOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//optionalinput = TUIConfig.getAppContext().getContentResolver().openInputStream(uri);bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions);input.close();compressImage(bitmap);bitmap = rotateBitmapByDegree(bitmap, degree);} catch (Exception e) {e.printStackTrace();}return bitmap;//再進(jìn)行質(zhì)量壓縮}public static Bitmap getBitmapFormPath(String path) {if (TextUtils.isEmpty(path)) {return null;}return getBitmapFormPath(Uri.fromFile(new File(path)));}public static Bitmap compressImage(Bitmap image) {ByteArrayOutputStream baos = new ByteArrayOutputStream();image.compress(Bitmap.CompressFormat.JPEG, 100, baos);//質(zhì)量壓縮方法,這里100表示不壓縮,把壓縮后的數(shù)據(jù)存放到baos中int options = 100;while (baos.toByteArray().length / 1024 > 100) { //循環(huán)判斷如果壓縮后圖片是否大于100kb,大于繼續(xù)壓縮baos.reset();//重置baos即清空baos//第一個(gè)參數(shù) :圖片格式 ,第二個(gè)參數(shù): 圖片質(zhì)量,100為最高,0為最差 ,第三個(gè)參數(shù):保存壓縮后的數(shù)據(jù)的流image.compress(Bitmap.CompressFormat.JPEG, options, baos);//這里壓縮options%,把壓縮后的數(shù)據(jù)存放到baos中options -= 10;//每次都減少10}ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());//把壓縮后的數(shù)據(jù)baos存放到ByteArrayInputStream中Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);//把ByteArrayInputStream數(shù)據(jù)生成圖片return bitmap;}/*** 讀取圖片的旋轉(zhuǎn)的角度*/public static int getBitmapDegree(Uri uri) {int degree = 0;try {// 從指定路徑下讀取圖片,并獲取其EXIF信息ExifInterface exifInterface = new ExifInterface(FileUtil.getPathFromUri(uri));// 獲取圖片的旋轉(zhuǎn)信息int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_NORMAL);switch (orientation) {case ExifInterface.ORIENTATION_ROTATE_90:degree = 90;break;case ExifInterface.ORIENTATION_ROTATE_180:degree = 180;break;case ExifInterface.ORIENTATION_ROTATE_270:degree = 270;break;}} catch (IOException e) {e.printStackTrace();}return degree;}/*** 讀取圖片的旋轉(zhuǎn)的角度*/public static int getBitmapDegree(String fileName) {int degree = 0;try {// 從指定路徑下讀取圖片,并獲取其EXIF信息ExifInterface exifInterface = new ExifInterface(fileName);// 獲取圖片的旋轉(zhuǎn)信息int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_NORMAL);switch (orientation) {case ExifInterface.ORIENTATION_ROTATE_90:degree = 90;break;case ExifInterface.ORIENTATION_ROTATE_180:degree = 180;break;case ExifInterface.ORIENTATION_ROTATE_270:degree = 270;break;}} catch (IOException e) {e.printStackTrace();}return degree;}/*** 將圖片按照某個(gè)角度進(jìn)行旋轉(zhuǎn)** @param bm 需要旋轉(zhuǎn)的圖片* @param degree 旋轉(zhuǎn)角度* @return 旋轉(zhuǎn)后的圖片*/public static Bitmap rotateBitmapByDegree(Bitmap bm, int degree) {Bitmap returnBm = null;// 根據(jù)旋轉(zhuǎn)角度,生成旋轉(zhuǎn)矩陣Matrix matrix = new Matrix();matrix.postRotate(degree);try {// 將原始圖片按照旋轉(zhuǎn)矩陣進(jìn)行旋轉(zhuǎn),并得到新的圖片returnBm = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true);} catch (OutOfMemoryError e) {}if (returnBm == null) {returnBm = bm;}if (bm != returnBm) {bm.recycle();}return returnBm;}public static int[] getImageSize(String path) {int size[] = new int[2];try {BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();onlyBoundsOptions.inJustDecodeBounds = true;BitmapFactory.decodeFile(path, onlyBoundsOptions);int originalWidth = onlyBoundsOptions.outWidth;int originalHeight = onlyBoundsOptions.outHeight;//size[0] = originalWidth;//size[1] = originalHeight;int degree = getBitmapDegree(path);if (degree == 0) {size[0] = originalWidth;size[1] = originalHeight;} else {//圖片分辨率以480x800為標(biāo)準(zhǔn)float hh = 800f;//這里設(shè)置高度為800ffloat ww = 480f;//這里設(shè)置寬度為480fif (degree == 90 || degree == 270) {hh = 480;ww = 800;}//縮放比。由于是固定比例縮放,只用高或者寬其中一個(gè)數(shù)據(jù)進(jìn)行計(jì)算即可int be = 1;//be=1表示不縮放if (originalWidth > originalHeight && originalWidth > ww) {//如果寬度大的話根據(jù)寬度固定大小縮放be = (int) (originalWidth / ww);} else if (originalWidth < originalHeight && originalHeight > hh) {//如果高度高的話根據(jù)寬度固定大小縮放be = (int) (originalHeight / hh);}if (be <= 0)be = 1;BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();bitmapOptions.inSampleSize = be;//設(shè)置縮放比例bitmapOptions.inDither = true;//optionalbitmapOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//optionalBitmap bitmap = BitmapFactory.decodeFile(path, bitmapOptions);bitmap = rotateBitmapByDegree(bitmap, degree);size[0] = bitmap.getWidth();size[1] = bitmap.getHeight();}} catch (Exception e) {e.printStackTrace();}return size;}// 圖片文件先在本地做旋轉(zhuǎn),返回旋轉(zhuǎn)之后的圖片文件路徑public static String getImagePathAfterRotate(final Uri uri) {try {InputStream is = TUIConfig.getAppContext().getContentResolver().openInputStream(uri);Bitmap originBitmap = BitmapFactory.decodeStream(is, null, null);int degree = ImageUtil.getBitmapDegree(uri);if (degree == 0) {return FileUtil.getPathFromUri(uri);} else {Bitmap newBitmap = ImageUtil.rotateBitmapByDegree(originBitmap, degree);String oldName = FileUtil.getFileName(TUIConfig.getAppContext(), uri);File newImageFile = FileUtil.generateFileName(oldName, FileUtil.getDocumentCacheDir(TUIConfig.getAppContext()));if (newImageFile == null) {return FileUtil.getPathFromUri(uri);}ImageUtil.storeBitmap(newImageFile, newBitmap);newBitmap.recycle();return newImageFile.getAbsolutePath();}} catch (FileNotFoundException e) {return FileUtil.getPathFromUri(uri);}}/*** 轉(zhuǎn)換圖片成圓形** @param bitmap 傳入Bitmap對(duì)象* @return*/public static Bitmap toRoundBitmap(Bitmap bitmap) {int width = bitmap.getWidth();int height = bitmap.getHeight();float roundPx;float left, top, right, bottom, dst_left, dst_top, dst_right, dst_bottom;if (width <= height) {roundPx = width / 2;left = 0;top = 0;right = width;bottom = width;height = width;dst_left = 0;dst_top = 0;dst_right = width;dst_bottom = width;} else {roundPx = height / 2;float clip = (width - height) / 2;left = clip;right = width - clip;top = 0;bottom = height;width = height;dst_left = 0;dst_top = 0;dst_right = height;dst_bottom = height;}Bitmap output = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(output);final int color = 0xff424242;final Paint paint = new Paint();final Rect src = new Rect((int) left, (int) top, (int) right, (int) bottom);final Rect dst = new Rect((int) dst_left, (int) dst_top, (int) dst_right, (int) dst_bottom);final RectF rectF = new RectF(dst);paint.setAntiAlias(true);// 設(shè)置畫筆無鋸齒canvas.drawARGB(0, 0, 0, 0); // 填充整個(gè)Canvaspaint.setColor(color);// 以下有兩種方法畫圓,drawRounRect和drawCircle// canvas.drawRoundRect(rectF, roundPx, roundPx, paint);// 畫圓角矩形,第一個(gè)參數(shù)為圖形顯示區(qū)域,第二個(gè)參數(shù)和第三個(gè)參數(shù)分別是水平圓角半徑和垂直圓角半徑。canvas.drawCircle(roundPx, roundPx, roundPx, paint);paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));// 設(shè)置兩張圖片相交時(shí)的模式canvas.drawBitmap(bitmap, src, dst, paint); //以Mode.SRC_IN模式合并bitmap和已經(jīng)draw了的Circlereturn output;}/*** 根據(jù)圖片 UUID 和 類型得到圖片文件路徑** @param uuid 圖片 UUID* @param imageType 圖片類型 V2TIMImageElem.V2TIM_IMAGE_TYPE_THUMB , V2TIMImageElem.V2TIM_IMAGE_TYPE_ORIGIN ,* V2TIMImageElem.V2TIM_IMAGE_TYPE_LARGE* @return 圖片文件路徑*/public static String generateImagePath(String uuid, int imageType) {return TUIConfig.getImageDownloadDir() + uuid + "_" + imageType;}public static String getGroupConversationAvatar(String groupId) {SharedPreferences sp = TUIConfig.getAppContext().getSharedPreferences(TUILogin.getSdkAppId() + SP_IMAGE, Context.MODE_PRIVATE);final String savedIcon = sp.getString(groupId, "");if (!TextUtils.isEmpty(savedIcon) && new File(savedIcon).isFile() && new File(savedIcon).exists()) {return savedIcon;}return "";}public static void setGroupConversationAvatar(String conversationId, String url) {SharedPreferences sp = TUIConfig.getAppContext().getSharedPreferences(TUILogin.getSdkAppId() + SP_IMAGE, Context.MODE_PRIVATE);SharedPreferences.Editor editor = sp.edit();editor.putString(conversationId, url);boolean success = editor.commit();if (!success) {Log.e("ImageUtil", "setGroupConversationAvatar failed , id : " + conversationId + " , url : " + url);}} }總結(jié)
以上是生活随笔為你收集整理的Android进阶之路 - 解决部分手机拍照之后图片被旋转的问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php 导出excel (html),p
- 下一篇: 数据库理论第八章部分作业——基于《数据库