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. *
* 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(); } }