Files
MoGoEagleEye/foudations/mogo-utils/src/main/java/com/mogo/utils/BitmapHelper.java
2019-12-23 15:08:04 +08:00

674 lines
23 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.mogo.utils;
import android.annotation.SuppressLint;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
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.RectF;
import android.media.ExifInterface;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.opengl.GLES10;
import android.os.Build;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
/**
* @author wangzhiyuan
* @since 2017/8/30
*/
public class BitmapHelper {
private static final String TAG = "BitmapHelper";
static int ONE_KB = 1024;
static int ONE_MB = ONE_KB * 1024;
static int SIZE_DEFAULT = 2048;
static int SIZE_LIMIT = 2048;
/**
* 根据原图添加圆角
*
* @param source
* @return
*/
public static Bitmap createRoundCornerImage( Bitmap source, float corner ) {
final Paint paint = new Paint();
paint.setAntiAlias( true );
Bitmap target = Bitmap.createBitmap( source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888 );
Canvas canvas = new Canvas( target );
RectF rect = new RectF( 0, 0, source.getWidth(), source.getHeight() );
canvas.drawRoundRect( rect, corner, corner, paint );
paint.setXfermode( new PorterDuffXfermode( PorterDuff.Mode.SRC_IN ) );
canvas.drawBitmap( source, 0, 0, paint );
return target;
}
public static byte[] bitmapToBytes( Bitmap bitmap ) {
if ( bitmap == null ) {
return null;
}
ByteArrayOutputStream bos = null;
byte[] result = null;
try {
bos = new ByteArrayOutputStream();
bitmap.compress( Bitmap.CompressFormat.JPEG, 100, bos );
result = bos.toByteArray();
} catch ( Exception e ) {
e.printStackTrace();
result = null;
} finally {
IOUtils.closeSilently( bos );
}
return result;
}
/**
* Use quality compression to compress bitmap's size to be smaller than a max size, and convert it to bytes thereafter.
* Note that this method will not report compressing ratio related data.
*
* @param bitmap data source
* @param maxSize unit in kb
* @return bytes after compressing bitmap to a size smaller than a specific max size.
*/
public static byte[] bitmapToBytes( Bitmap bitmap, int maxSize ) {
final long start = System.currentTimeMillis();
if ( bitmap == null ) {
return null;
}
final int maxSizeOfBytes = maxSize * ONE_KB;
ByteArrayOutputStream bos = null;
byte[] result = null;
try {
bos = new ByteArrayOutputStream();
int quality = 100;
int fullSize = 0;
do {
bos.reset();
bitmap.compress( Bitmap.CompressFormat.JPEG, quality, bos );
if ( quality == 100 ) {
fullSize = bos.size();
}
Log.i( TAG, "quality<---->size, " + quality + "<---->" + bos.size() / 1024 );
}
while ( bos.size() > maxSizeOfBytes && ( quality -= ( fullSize > ONE_MB ) ? 10 : 5 ) >= 0 );
result = bos.toByteArray();
final long end = System.currentTimeMillis();
Log.i( TAG,
"bitmap to bytes costs " + ( end - start ) + "ms, \n" +
"bitmap full size is " + ( fullSize / 1024 ) + "kb, \n" +
"bitmap final size is " + ( bos.size() / 1024 ) + "kb, \n" +
"bitmap quality is " + quality );
} catch ( Exception e ) {
e.printStackTrace();
result = null;
} finally {
IOUtils.closeSilently( bos );
}
return result;
}
/**
* Use quality compression to compress bitmap to be smaller than a specific max size.
*
* @param bitmap data source
* @param maxSize a specific max size which's unit is kb.
* @return a compressed bitmap smaller than the max size.
*/
public static Bitmap compressBitmap( Bitmap bitmap, int maxSize, final OnCompressListener listener ) {
if ( bitmap == null || bitmap.isRecycled() ) {
return null;
}
listener.onBeforeCompress();
ByteArrayOutputStream bos = null;
Bitmap target = null;
try {
bos = new ByteArrayOutputStream();
int quality = 100;
do {
bos.reset();
bitmap.compress( Bitmap.CompressFormat.JPEG, quality, bos );
}
while ( bos.size() / 1024 > maxSize && ( quality -= 5 ) >= 0 );
byte[] result = bos.toByteArray();
target = bytesToBitmap( result );
if ( listener != null ) {
listener.onCompressSuccess( result );
}
} catch ( Exception e ) {
e.printStackTrace();
target = null;
if ( listener != null ) {
listener.onCompressFailed( "压缩失败" );
}
} finally {
IOUtils.closeSilently( bos );
}
return target;
}
public static Bitmap compressBitmap( Bitmap bitmap, int maxSize ) {
if ( bitmap == null ) {
return null;
}
ByteArrayOutputStream bos = null;
Bitmap target = null;
try {
bos = new ByteArrayOutputStream();
int quality = 100;
do {
bos.reset();
bitmap.compress( Bitmap.CompressFormat.JPEG, quality, bos );
}
while ( bos.size() / 1024 > maxSize && ( quality -= 5 ) >= 0 );
byte[] result = bos.toByteArray();
target = bytesToBitmap( result );
} catch ( Exception e ) {
e.printStackTrace();
target = null;
} finally {
IOUtils.closeSilently( bos );
}
return target;
}
/**
* Decode an immutable bitmap from the specified byte array.
*
* @param b byte array of compressed image data
* @return an immutable bitmap or null in case of exception.
*/
public static Bitmap bytesToBitmap( byte[] b ) {
if ( b != null && b.length != 0 ) {
return BitmapFactory.decodeByteArray( b, 0, b.length );
} else {
return null;
}
}
/**
* Decode an immutable bitmap from the specified byte array.
*
* @param b byte array of compressed image data
* @param options Options that control downsampling and whether the
* image should be completely decoded, or just is size returned.
* @return an immutable bitmap or null in case of exception.
*/
public static Bitmap bytesToBitmap( byte[] b, BitmapFactory.Options options ) {
if ( b.length != 0 ) {
return BitmapFactory.decodeByteArray( b, 0, b.length, options );
} else {
return null;
}
}
/**
* Get max supported image size which will differ from different devices.
*
* @return max size related to the device.
*/
public static int getMaxSupportedImageSize() {
int textureLimit = getMaxTextureSize();
if ( textureLimit == 0 ) {
return SIZE_DEFAULT;
} else {
return Math.min( textureLimit, SIZE_LIMIT );
}
}
public static int getMaxTextureSize2() {
// The OpenGL texture size is the maximum size that can be drawn in an ImageView
int[] maxSize = new int[1];
GLES10.glGetIntegerv( GLES10.GL_MAX_TEXTURE_SIZE, maxSize, 0 );
return maxSize[0];
}
/**
* Decode a bitmap's input stream to find a proper inSampleSize according to device's max supported size.
*
* @param is bitmap's data source
* @param close whether to close input stream after work is done.
* @return a proper inSampleSize
*/
public static int findProperInSampleSize( InputStream is, boolean close ) {
// Just decode image size into options
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
try {
BitmapFactory.decodeStream( is, null, options );
} catch ( Exception e ) {
e.printStackTrace();
} finally {
if ( close ) IOUtils.closeSilently( is );
}
int maxSize = getMaxSupportedImageSize();
int sampleSize = 1;
while ( options.outHeight / sampleSize > maxSize || options.outWidth / sampleSize > maxSize ) {
sampleSize = sampleSize << 1;
}
Log.i( TAG, "sample size is " + sampleSize );
return sampleSize;
}
/**
* Read a picture's degree from a file.
*
* @param file data source of a picture
* @return degrees range from 0 to 360
*/
public static int readPictureDegree( File file ) {
return readPictureDegree( file.getAbsolutePath() );
}
/**
* Read a picture's degree from a file, we use {@link ExifInterface} instead of {@link android.media.ExifInterface}
* to avoid some unexpected bugs.
*
* @param filePath file's absolute path which we can read data source of a picture from.
* @return degrees range from 0 to 360
*/
public static int readPictureDegree( String filePath ) {
int degree = 0;
try {
ExifInterface exifInterface = new ExifInterface( filePath );
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 ( Exception e ) {
e.printStackTrace();
}
Log.i( TAG, "ExifInterface, degree is " + degree );
return degree;
}
/**
* Rotate an bitmap to a specific angle.
*
* @param angle target angle
* @param bitmap data source
* @return Returns an immutable bitmap from subset of the source bitmap,
* transformed by the optional matrix. The new bitmap may be the
* same object as source, or a copy may have been made. It is
* initialized with the same density as the original bitmap.
* <p>
* If the source bitmap is immutable and the requested subset is the
* same as the source bitmap itself, then the source bitmap is
* returned and no new bitmap is created.
*/
public static Bitmap rotateBitmap( int angle, Bitmap bitmap ) {
if ( bitmap == null ) {
return null;
}
try {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
Matrix matrix = new Matrix();
matrix.preRotate( angle );
return Bitmap.createBitmap( bitmap, 0, 0, width, height, matrix, true );
} catch ( Exception e ) {
e.printStackTrace();
return bitmap;
}
}
/**
* Get picture's absolute path according to its uri.
*
* @param context context
* @param uri picture's uri
* @return absolute path of uri.
*/
public static String getRealPathFromUri( Context context, Uri uri ) {
int sdkVersion = Build.VERSION.SDK_INT;
if ( sdkVersion >= 19 ) {
return getRealPathFromUriAboveApi19( context, uri );
} else {
return getRealPathFromUriBelowAPI19( context, uri );
}
}
/**
* Create a default {@link BitmapFactory.Options} .
* Note this options use rgb_565 and a proper inSampleSize in order to save memory.
*
* @param is data source of picture
* @param close whether to close data source
* @return options containing rgb_565 config and a proper inSampleSize.
*/
public static BitmapFactory.Options newDefaultOptions( InputStream is, boolean close ) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inSampleSize = BitmapHelper.findProperInSampleSize( is, close );
return options;
}
/**
* Save picture to local file.
*
* @param bitmap data source
* @param file local file to store picture.
*/
public static void savePicture( Bitmap bitmap, File file ) {
final long start = System.currentTimeMillis();
if ( bitmap == null || file == null ) {
Log.i( TAG, "保存失败, bitmap or file is null." );
return;
}
if ( file.getParentFile() != null && !file.getParentFile().exists() ) {
file.getParentFile().mkdirs();
}
try {
final FileOutputStream fos = new FileOutputStream( file );
bitmap.compress( Bitmap.CompressFormat.JPEG, 100, fos );
fos.flush();
fos.close();
if ( file.exists() ) {
Log.i( TAG, "保存成功" );
}
} catch ( Exception e ) {
e.printStackTrace();
}
Log.i( TAG, "saving picture costs " + ( System.currentTimeMillis() - start ) + "ms" );
}
/**
* 适配api19以下(不包括api19),根据uri获取图片的绝对路径
*
* @param context 上下文对象
* @param uri 图片的Uri
* @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null
*/
private static String getRealPathFromUriBelowAPI19( Context context, Uri uri ) {
return getDataColumn( context, uri, null, null );
}
/**
* 适配api19及以上,根据uri获取图片的绝对路径
*
* @param context 上下文对象
* @param uri 图片的Uri
* @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null
*/
@SuppressLint( "NewApi" )
private static String getRealPathFromUriAboveApi19( Context context, Uri uri ) {
String filePath = null;
try {
// 如果是document类型的 uri, 则通过document id来进行处理
if ( DocumentsContract.isDocumentUri( context, uri ) ) {
String documentId = DocumentsContract.getDocumentId( uri );
if ( isMediaDocument( uri ) ) {
// 使用':'分割
String id = documentId.split( ":" )[1];
String selection = MediaStore.Images.Media._ID + "=?";
String[] selectionArgs = {id};
filePath = getDataColumn( context, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection, selectionArgs );
} else if ( isDownloadsDocument( uri ) ) {
Uri contentUri = ContentUris.withAppendedId( Uri.parse( "content://downloads/public_downloads" ), Long.valueOf( documentId ) );
filePath = getDataColumn( context, contentUri, null, null );
}
} else if ( "content".equalsIgnoreCase( uri.getScheme() ) ) {
filePath = getDataColumn( context, uri, null, null );
} else if ( "file".equals( uri.getScheme() ) ) {
filePath = uri.getPath();
}
} catch ( Exception e ) {
e.printStackTrace();
}
return filePath;
}
/**
* 获取数据库表中的 _data 列即返回Uri对应的文件路径
*/
private static String getDataColumn( Context context, Uri uri, String selection, String[] selectionArgs ) {
String path = null;
String[] projection = new String[]{MediaStore.Images.Media.DATA};
Cursor cursor = null;
try {
cursor = context.getContentResolver().query( uri, projection, selection, selectionArgs, null );
if ( cursor != null && cursor.moveToFirst() ) {
int columnIndex = cursor.getColumnIndexOrThrow( projection[0] );
path = cursor.getString( columnIndex );
}
} catch ( Exception e ) {
if ( cursor != null ) {
cursor.close();
cursor = null;
}
} finally {
if ( cursor != null ) {
cursor.close();
cursor = null;
}
}
return path;
}
/**
* @param uri the Uri to check
* @return Whether the Uri authority is MediaProvider
*/
private static boolean isMediaDocument( Uri uri ) {
return "com.android.providers.media.documents".equals( uri.getAuthority() );
}
/**
* @param uri the Uri to check
* @return Whether the Uri authority is DownloadsProvider
*/
private static boolean isDownloadsDocument( Uri uri ) {
return "com.android.providers.downloads.documents".equals( uri.getAuthority() );
}
public static int getMaxTextureSize() {
try {
// Safe minimum default size
final int IMAGE_MAX_BITMAP_DIMENSION = SIZE_DEFAULT;
// Get EGL Display
EGL10 egl = ( EGL10 ) EGLContext.getEGL();
EGLDisplay display = egl.eglGetDisplay( EGL10.EGL_DEFAULT_DISPLAY );
// Initialise
int[] version = new int[2];
egl.eglInitialize( display, version );
// Query total number of configurations
int[] totalConfigurations = new int[1];
egl.eglGetConfigs( display, null, 0, totalConfigurations );
// Query actual list configurations
EGLConfig[] configurationsList = new EGLConfig[totalConfigurations[0]];
egl.eglGetConfigs( display, configurationsList, totalConfigurations[0], totalConfigurations );
int[] textureSize = new int[1];
int maximumTextureSize = 0;
// Iterate through all the configurations to located the maximum texture size
for ( int i = 0; i < totalConfigurations[0]; i++ ) {
// Only need to check for width since opengl textures are always squared
egl.eglGetConfigAttrib( display, configurationsList[i], EGL10.EGL_MAX_PBUFFER_WIDTH, textureSize );
// Keep trackCustomEvent of the maximum texture size
if ( maximumTextureSize < textureSize[0] )
maximumTextureSize = textureSize[0];
}
// Release
egl.eglTerminate( display );
// Return largest texture size found, or default
return Math.max( maximumTextureSize, IMAGE_MAX_BITMAP_DIMENSION );
} catch ( Exception e ) {
e.printStackTrace();
}
return 0;
}
public static String bitmapToBase64( Bitmap bitmap ) {
String result = null;
try {
if ( bitmap != null ) {
result = Base64.encodeToString( bitmapToBytes( bitmap ), Base64.DEFAULT );
}
} catch ( Exception e ) {
e.printStackTrace();
}
return result;
}
public static Bitmap base64ToBitmap( String base64Data ) {
byte[] bytes = Base64.decode( base64Data, Base64.DEFAULT );
return BitmapFactory.decodeByteArray( bytes, 0, bytes.length );
}
/**
* 在系统返回的intent中获取图片信息并转化为uri
*
* @param data
* @return
*/
public static Uri convertUri( Context context, Intent data ) {
if ( data == null || data.getData() == null ) {
return null;
}
Uri localUri = data.getData();
String scheme = localUri.getScheme();
String imagePath = "";
if ( "content".equals( scheme ) ) {
String[] filePathColumns = {MediaStore.Images.Media.DATA};
Cursor c = context.getContentResolver().query( localUri, filePathColumns, null, null, null );
if ( c != null ) {
c.moveToFirst();
int columnIndex = c.getColumnIndex( filePathColumns[0] );
imagePath = c.getString( columnIndex );
c.close();
}
} else if ( "file".equals( scheme ) ) {//小米4选择云相册中的图片是根据此方法获得路径
imagePath = localUri.getPath();
}
if ( TextUtils.isEmpty( imagePath ) ) {
return localUri;
}
Uri uri = Uri.fromFile( new File( imagePath ) );
return uri != null ? uri : localUri;
}
public static Bitmap colorToBitmap( Context context, int colorResId ) {// drawable 转换成bitmap
Bitmap.Config config = Bitmap.Config.ARGB_8888;// 取drawable的颜色格式
Bitmap bitmap = Bitmap.createBitmap( 1, 1, config );// 建立对应bitmap
bitmap.eraseColor( context.getResources().getColor( colorResId ) );
return bitmap;
}
public static String getAlphaHexValue( float alpha ) {
String color = Integer.toHexString( ( int ) alpha * 255 );
return TextUtils.isEmpty( color ) ? color : color.toUpperCase();
}
/**
* 抓取本地视频缩略图(操作可能耗时,尽量异步进行)
*
* @param filePath
* @return
*/
public static Bitmap getVideoThumbnail( String filePath ) {
Bitmap b = null;
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
try {
retriever.setDataSource( filePath );
b = retriever.getFrameAtTime();
} catch ( IllegalArgumentException e ) {
e.printStackTrace();
} catch ( RuntimeException e ) {
e.printStackTrace();
} finally {
try {
retriever.release();
} catch ( RuntimeException e ) {
e.printStackTrace();
}
}
return b;
}
public interface OnCompressListener {
void onCompressSuccess( byte[] data );
void onCompressFailed( String msg );
void onBeforeCompress();
}
}