Browse Source

优化算法,压缩速度更快,压缩效果更好

zibin 7 years ago
parent
commit
9f8d1ea298

+ 2 - 12
.gitignore

@@ -1,15 +1,5 @@
 *.iml
 .gradle
 /local.properties
-/.idea/workspace.xml
-/.idea/libraries
-.DS_Store
-/build
-/captures
-.idea/misc.xml
-vcs.xml
-
-.idea/misc.xml
-libs/
-src/main/res/drawable/
-.idea
+/.idea
+/build

+ 1 - 1
build.gradle

@@ -5,7 +5,7 @@ buildscript {
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:2.3.2'
+        classpath 'com.android.tools.build:gradle:2.3.3'
 
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files

+ 6 - 6
example/build.gradle

@@ -1,13 +1,13 @@
 apply plugin: 'com.android.application'
 
 android {
-    compileSdkVersion 24
-    buildToolsVersion '25.0.0'
+    compileSdkVersion 25
+    buildToolsVersion '25.0.2'
 
     defaultConfig {
         applicationId "top.zibin.luban.example"
         minSdkVersion 11
-        targetSdkVersion 24
+        targetSdkVersion 25
         versionCode 1
         versionName "1.0"
     }
@@ -22,13 +22,13 @@ android {
 dependencies {
     compile fileTree(include: ['*.jar'], dir: 'libs')
     testCompile 'junit:junit:4.12'
-    compile 'com.android.support:appcompat-v7:24.2.0'
-    compile 'com.android.support:design:24.2.0'
+    compile 'com.android.support:appcompat-v7:25.3.1'
+    compile 'com.android.support:design:25.3.1'
     compile 'io.reactivex:rxandroid:1.1.0'
     compile 'io.reactivex:rxjava:1.1.3'
 
     compile 'com.nineoldandroids:library:2.4.0'
     compile 'com.github.bumptech.glide:glide:3.7.0'
-    compile 'me.iwf.photopicker:PhotoPicker:0.8.5@aar'
+    compile 'me.iwf.photopicker:PhotoPicker:0.9.5@aar'
     compile project(':library')
 }

+ 17 - 40
example/src/main/java/top/zibin/luban/example/MainActivity.java

@@ -1,6 +1,7 @@
 package top.zibin.luban.example;
 
 import android.content.Intent;
+import android.graphics.BitmapFactory;
 import android.net.Uri;
 import android.os.Bundle;
 import android.support.design.widget.FloatingActionButton;
@@ -8,6 +9,7 @@ import android.support.v7.app.AppCompatActivity;
 import android.support.v7.widget.Toolbar;
 import android.util.Log;
 import android.view.View;
+import android.widget.Button;
 import android.widget.ImageView;
 import android.widget.TextView;
 import android.widget.Toast;
@@ -23,6 +25,7 @@ import rx.android.schedulers.AndroidSchedulers;
 import rx.functions.Action1;
 import rx.functions.Func1;
 import rx.schedulers.Schedulers;
+import top.zibin.luban.Luban;
 import top.zibin.luban.LubanOld;
 import top.zibin.luban.OnCompressListener;
 
@@ -47,7 +50,7 @@ public class MainActivity extends AppCompatActivity {
     thumbImageSize = (TextView) findViewById(R.id.thumb_image_size);
     image = (ImageView) findViewById(R.id.image);
 
-    FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
+    Button fab = (Button) findViewById(R.id.fab);
     fab.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View view) {
@@ -57,7 +60,6 @@ public class MainActivity extends AppCompatActivity {
             .setShowGif(true)
             .setPreviewEnabled(false)
             .start(MainActivity.this, PhotoPicker.REQUEST_CODE);
-
       }
     });
   }
@@ -66,9 +68,8 @@ public class MainActivity extends AppCompatActivity {
    * 压缩单张图片 Listener 方式
    */
   private void compressWithLs(File file) {
-    LubanOld.get(this)
+    Luban.get(this)
         .load(file)
-        .putGear(LubanOld.THIRD_GEAR)
         .setCompressListener(new OnCompressListener() {
           @Override
           public void onStart() {
@@ -82,7 +83,7 @@ public class MainActivity extends AppCompatActivity {
             Glide.with(MainActivity.this).load(file).into(image);
 
             thumbFileSize.setText(file.length() / 1024 + "k");
-            thumbImageSize.setText(LubanOld.get(getApplicationContext()).getImageSize(file.getPath())[0] + " * " + LubanOld.get(getApplicationContext()).getImageSize(file.getPath())[1]);
+            thumbImageSize.setText(computeSize(file)[0] + "*" + computeSize(file)[1]);
           }
 
           @Override
@@ -92,42 +93,18 @@ public class MainActivity extends AppCompatActivity {
         }).launch();
   }
 
-  /**
-   * 压缩单张图片 RxJava 方式
-   */
-  private void compressWithRx(File file) {
-    LubanOld.get(this)
-        .load(file)
-        .putGear(LubanOld.THIRD_GEAR)
-        .asObservable()
-        .subscribeOn(Schedulers.io())
-        .observeOn(AndroidSchedulers.mainThread())
-        .doOnError(new Action1<Throwable>() {
-          @Override
-          public void call(Throwable throwable) {
-            throwable.printStackTrace();
-          }
-        })
-        .onErrorResumeNext(new Func1<Throwable, Observable<? extends File>>() {
-          @Override
-          public Observable<? extends File> call(Throwable throwable) {
-            return Observable.empty();
-          }
-        })
-        .subscribe(new Action1<File>() {
-          @Override
-          public void call(File file) {
-            Glide.with(MainActivity.this).load(file).into(image);
+  private int[] computeSize(File srcImg) {
+    int[] size = new int[2];
 
-            Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
-            Uri uri = Uri.fromFile(file);
-            intent.setData(uri);
-            MainActivity.this.sendBroadcast(intent);
+    BitmapFactory.Options options = new BitmapFactory.Options();
+    options.inJustDecodeBounds = true;
+    options.inSampleSize = 1;
 
-            thumbFileSize.setText(file.length() / 1024 + "k");
-            thumbImageSize.setText(LubanOld.get(getApplicationContext()).getImageSize(file.getPath())[0] + " * " + LubanOld.get(getApplicationContext()).getImageSize(file.getPath())[1]);
-          }
-        });
+    BitmapFactory.decodeFile(srcImg.getAbsolutePath(), options);
+    size[0] = options.outWidth;
+    size[1] = options.outHeight;
+
+    return size;
   }
 
   @Override
@@ -140,7 +117,7 @@ public class MainActivity extends AppCompatActivity {
 
         File imgFile = new File(photos.get(0));
         fileSize.setText(imgFile.length() / 1024 + "k");
-        imageSize.setText(LubanOld.get(this).getImageSize(imgFile.getPath())[0] + " * " + LubanOld.get(this).getImageSize(imgFile.getPath())[1]);
+        imageSize.setText(computeSize(imgFile)[0] + "*" + computeSize(imgFile)[1]);
 
         for (int i = 0; i < photos.size(); i++)
           compressWithLs(new File(photos.get(i)));

+ 230 - 213
example/src/main/java/top/zibin/luban/example/PathUtils.java

@@ -26,240 +26,257 @@ import java.io.File;
  */
 public class PathUtils {
 
-    public final static String SDCARD_MNT = "/mnt/sdcard";
-    public final static String SDCARD = Environment.getExternalStorageDirectory().getPath();
-
-    /**
-     * <b>BuildTime:</b> 2014-10-22<br>
-     * <b>Description:</b> get SDCard path<br>
-     *
-     * @return String of path
-     */
-    public static String getSDCardPath() {
-        return Environment.getExternalStorageDirectory().getPath();
+  public final static String SDCARD_MNT = "/mnt/sdcard";
+  public final static String SDCARD = Environment.getExternalStorageDirectory().getPath();
+
+  /**
+   * <b>BuildTime:</b> 2014-10-22<br>
+   * <b>Description:</b> get SDCard path<br>
+   *
+   * @return String of path
+   */
+  public static String getSDCardPath() {
+    return Environment.getExternalStorageDirectory().getPath();
+  }
+
+  /**
+   * <b>BuildTime:</b> 2014年10月23日<br>
+   * <b>Description:</b> <br>
+   *
+   * @param mUri
+   *
+   * @return
+   */
+  public static String getAbsolutePathFromNoStandardUri(Uri mUri) {
+    String filePath = null;
+
+    String mUriString = mUri.toString();
+    mUriString = Uri.decode(mUriString);
+
+    String pre1 = "file://" + SDCARD + File.separator;
+    String pre2 = "file://" + SDCARD_MNT + File.separator;
+
+    if (mUriString.startsWith(pre1)) {
+      filePath = Environment.getExternalStorageDirectory().getPath()
+          + File.separator + mUriString.substring(pre1.length());
+    } else if (mUriString.startsWith(pre2)) {
+      filePath = Environment.getExternalStorageDirectory().getPath()
+          + File.separator + mUriString.substring(pre2.length());
     }
-
-    /**
-     * <b>BuildTime:</b> 2014年10月23日<br>
-     * <b>Description:</b> <br>
-     *
-     * @param mUri
-     * @return
-     */
-    public static String getAbsolutePathFromNoStandardUri(Uri mUri) {
-        String filePath = null;
-
-        String mUriString = mUri.toString();
-        mUriString = Uri.decode(mUriString);
-
-        String pre1 = "file://" + SDCARD + File.separator;
-        String pre2 = "file://" + SDCARD_MNT + File.separator;
-
-        if (mUriString.startsWith(pre1)) {
-            filePath = Environment.getExternalStorageDirectory().getPath()
-                    + File.separator + mUriString.substring(pre1.length());
-        } else if (mUriString.startsWith(pre2)) {
-            filePath = Environment.getExternalStorageDirectory().getPath()
-                    + File.separator + mUriString.substring(pre2.length());
-        }
-        return filePath;
+    return filePath;
+  }
+
+  /**
+   * <b>BuildTime:</b> 2014年10月23日<br>
+   * <b>Description:</b> Use the uri to get the file path<br>
+   *
+   * @param c
+   * @param uri
+   *
+   * @return
+   */
+  public static String getAbsoluteUriPath(Context c, Uri uri) {
+    String imgPath = "";
+    String[] proj = {MediaStore.Images.Media.DATA};
+    Cursor cursor = new CursorLoader(c, uri, proj, null, null, null).loadInBackground();
+
+    if (cursor != null) {
+      int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
+      if (cursor.getCount() > 0 && cursor.moveToFirst()) {
+        imgPath = cursor.getString(column_index);
+      }
     }
 
-    /**
-     * <b>BuildTime:</b> 2014年10月23日<br>
-     * <b>Description:</b> Use the uri to get the file path<br>
-     *
-     * @param c
-     * @param uri
-     * @return
-     */
-    public static String getAbsoluteUriPath(Context c, Uri uri) {
-        String imgPath = "";
-        String[] proj = {MediaStore.Images.Media.DATA};
-        Cursor cursor = new CursorLoader(c, uri, proj, null, null, null).loadInBackground();
-
-        if (cursor != null) {
-            int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
-            if (cursor.getCount() > 0 && cursor.moveToFirst()) {
-                imgPath = cursor.getString(column_index);
-            }
-        }
-
-        return imgPath;
+    return imgPath;
+  }
+
+  /**
+   * <b>BuildTime:</b> 2014-8-30<br>
+   * <b>Description:</b> Get the external cache directory,it will be bulid a
+   * directory what is name "Android/data/PACKAGE_NAME/cache" for 2.2 system"<br>
+   *
+   * @param context
+   *
+   * @return
+   */
+  public static File getExternalCacheDir(Context context) {
+    if (hasExternalCacheDir()) {
+      return context.getExternalCacheDir();
     }
 
-    /**
-     * <b>BuildTime:</b> 2014-8-30<br>
-     * <b>Description:</b> Get the external cache directory,it will be bulid a
-     * directory what is name "Android/data/PACKAGE_NAME/cache" for 2.2 system"<br>
-     *
-     * @param context
-     * @return
-     */
-    public static File getExternalCacheDir(Context context) {
-        if (hasExternalCacheDir()) {
-            return context.getExternalCacheDir();
-        }
-
-        final String cacheDir = "/Android/data/" + context.getPackageName() + "/cache/";
-        return new File(Environment.getExternalStorageDirectory().getPath() + cacheDir);
+    final String cacheDir = "/Android/data/" + context.getPackageName() + "/cache/";
+    return new File(Environment.getExternalStorageDirectory().getPath() + cacheDir);
+  }
+
+  /**
+   * <b>BuildTime:</b> 2014-8-30<br>
+   * <b>Description:</b> Check directory,if null,create it<br>
+   *
+   * @param parent
+   * @param dirName
+   *
+   * @return
+   */
+  public static File findOrCreateDir(File parent, String dirName) {
+    File directory = new File(parent, dirName);
+    if (!directory.exists()) {
+      directory.mkdirs();
     }
+    return directory;
+  }
 
-    /**
-     * <b>BuildTime:</b> 2014-8-30<br>
-     * <b>Description:</b> Check directory,if null,create it<br>
-     *
-     * @param parent
-     * @param dirName
-     * @return
-     */
-    public static File findOrCreateDir(File parent, String dirName) {
-        File directory = new File(parent, dirName);
-        if (!directory.exists()) {
-            directory.mkdirs();
-        }
-        return directory;
-    }
+  private static boolean hasExternalCacheDir() {
+    return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO;
+  }
 
-    private static boolean hasExternalCacheDir() {
-        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO;
-    }
+  @TargetApi(Build.VERSION_CODES.KITKAT)
+  public static String getPath(final Context context, final Uri uri) {
 
-    @TargetApi(Build.VERSION_CODES.KITKAT)
-    public static String getPath(final Context context, final Uri uri) {
-
-        final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
-
-        // DocumentProvider
-        if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
-            // ExternalStorageProvider
-            if (isExternalStorageDocument(uri)) {
-                final String docId = DocumentsContract.getDocumentId(uri);
-                final String[] split = docId.split(":");
-                final String type = split[0];
-
-                if ("primary".equalsIgnoreCase(type)) {
-                    return Environment.getExternalStorageDirectory() + "/" + split[1];
-                }
-
-                // TODO handle non-primary volumes
-            }
-            // DownloadsProvider
-            else if (isDownloadsDocument(uri)) {
-
-                final String id = DocumentsContract.getDocumentId(uri);
-                final Uri contentUri = ContentUris.withAppendedId(
-                        Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
-
-                return getDataColumn(context, contentUri, null, null);
-            }
-            // MediaProvider
-            else if (isMediaDocument(uri)) {
-                final String docId = DocumentsContract.getDocumentId(uri);
-                final String[] split = docId.split(":");
-                final String type = split[0];
-
-                Uri contentUri = null;
-                switch (type) {
-                    case "image":
-                        contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
-                        break;
-                    case "video":
-                        contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
-                        break;
-                    case "audio":
-                        contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
-                        break;
-                }
-
-                final String selection = "_id=?";
-                final String[] selectionArgs = new String[]{
-                        split[1]
-                };
-
-                return getDataColumn(context, contentUri, selection, selectionArgs);
-            }
-        }
-        // MediaStore (and general)
-        else if ("content".equalsIgnoreCase(uri.getScheme())) {
+    final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
 
-            // Return the remote address
-            if (isGooglePhotosUri(uri))
-                return uri.getLastPathSegment();
+    // DocumentProvider
+    if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
+      // ExternalStorageProvider
+      if (isExternalStorageDocument(uri)) {
+        final String docId = DocumentsContract.getDocumentId(uri);
+        final String[] split = docId.split(":");
+        final String type = split[0];
 
-            return getDataColumn(context, uri, null, null);
-        }
-        // File
-        else if ("file".equalsIgnoreCase(uri.getScheme())) {
-            return uri.getPath();
+        if ("primary".equalsIgnoreCase(type)) {
+          return Environment.getExternalStorageDirectory() + "/" + split[1];
         }
 
-        return null;
-    }
+        // TODO handle non-primary volumes
+      }
+      // DownloadsProvider
+      else if (isDownloadsDocument(uri)) {
+
+        final String id = DocumentsContract.getDocumentId(uri);
+        final Uri contentUri = ContentUris.withAppendedId(
+            Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
+
+        return getDataColumn(context, contentUri, null, null);
+      }
+      // MediaProvider
+      else if (isMediaDocument(uri)) {
+        final String docId = DocumentsContract.getDocumentId(uri);
+        final String[] split = docId.split(":");
+        final String type = split[0];
+
+        Uri contentUri = null;
+        switch (type) {
+          case "image":
+            contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+            break;
+          case "video":
+            contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+            break;
+          case "audio":
+            contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+            break;
+        }
 
-    /**
-     * Get the value of the data column for this Uri. This is useful for
-     * MediaStore Uris, and other file-based ContentProviders.
-     *
-     * @param context       The context.
-     * @param uri           The Uri to query.
-     * @param selection     (Optional) Filter used in the query.
-     * @param selectionArgs (Optional) Selection arguments used in the query.
-     * @return The value of the _data column, which is typically a file path.
-     */
-    public static String getDataColumn(Context context, Uri uri, String selection,
-                                       String[] selectionArgs) {
-
-        Cursor cursor = null;
-        final String column = "_data";
-        final String[] projection = {
-                column
+        final String selection = "_id=?";
+        final String[] selectionArgs = new String[]{
+            split[1]
         };
 
-        try {
-            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
-                    null);
-            if (cursor != null && cursor.moveToFirst()) {
-                final int index = cursor.getColumnIndexOrThrow(column);
-                return cursor.getString(index);
-            }
-        } finally {
-            if (cursor != null)
-                cursor.close();
-        }
-        return null;
+        return getDataColumn(context, contentUri, selection, selectionArgs);
+      }
     }
+    // MediaStore (and general)
+    else if ("content".equalsIgnoreCase(uri.getScheme())) {
 
+      // Return the remote address
+      if (isGooglePhotosUri(uri))
+        return uri.getLastPathSegment();
 
-    /**
-     * @param uri The Uri to check.
-     * @return Whether the Uri authority is ExternalStorageProvider.
-     */
-    public static boolean isExternalStorageDocument(Uri uri) {
-        return "com.android.externalstorage.documents".equals(uri.getAuthority());
-    }
-
-    /**
-     * @param uri The Uri to check.
-     * @return Whether the Uri authority is DownloadsProvider.
-     */
-    public static boolean isDownloadsDocument(Uri uri) {
-        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
+      return getDataColumn(context, uri, null, null);
     }
-
-    /**
-     * @param uri The Uri to check.
-     * @return Whether the Uri authority is MediaProvider.
-     */
-    public static boolean isMediaDocument(Uri uri) {
-        return "com.android.providers.media.documents".equals(uri.getAuthority());
+    // File
+    else if ("file".equalsIgnoreCase(uri.getScheme())) {
+      return uri.getPath();
     }
 
-    /**
-     * @param uri The Uri to check.
-     * @return Whether the Uri authority is Google Photos.
-     */
-    public static boolean isGooglePhotosUri(Uri uri) {
-        return "com.google.android.apps.photos.content".equals(uri.getAuthority());
+    return null;
+  }
+
+  /**
+   * Get the value of the data column for this Uri. This is useful for
+   * MediaStore Uris, and other file-based ContentProviders.
+   *
+   * @param context
+   *     The context.
+   * @param uri
+   *     The Uri to query.
+   * @param selection
+   *     (Optional) Filter used in the query.
+   * @param selectionArgs
+   *     (Optional) Selection arguments used in the query.
+   *
+   * @return The value of the _data column, which is typically a file path.
+   */
+  public static String getDataColumn(Context context, Uri uri, String selection,
+                                     String[] selectionArgs) {
+
+    Cursor cursor = null;
+    final String column = "_data";
+    final String[] projection = {
+        column
+    };
+
+    try {
+      cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
+          null);
+      if (cursor != null && cursor.moveToFirst()) {
+        final int index = cursor.getColumnIndexOrThrow(column);
+        return cursor.getString(index);
+      }
+    } finally {
+      if (cursor != null)
+        cursor.close();
     }
+    return null;
+  }
+
+
+  /**
+   * @param uri
+   *     The Uri to check.
+   *
+   * @return Whether the Uri authority is ExternalStorageProvider.
+   */
+  public static boolean isExternalStorageDocument(Uri uri) {
+    return "com.android.externalstorage.documents".equals(uri.getAuthority());
+  }
+
+  /**
+   * @param uri
+   *     The Uri to check.
+   *
+   * @return Whether the Uri authority is DownloadsProvider.
+   */
+  public static boolean isDownloadsDocument(Uri uri) {
+    return "com.android.providers.downloads.documents".equals(uri.getAuthority());
+  }
+
+  /**
+   * @param uri
+   *     The Uri to check.
+   *
+   * @return Whether the Uri authority is MediaProvider.
+   */
+  public 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 Google Photos.
+   */
+  public static boolean isGooglePhotosUri(Uri uri) {
+    return "com.google.android.apps.photos.content".equals(uri.getAuthority());
+  }
 }

+ 32 - 31
example/src/main/res/layout/activity_main.xml

@@ -1,39 +1,40 @@
 <?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:fitsSystemWindows="true"
-    android:orientation="vertical">
+<LinearLayout
+  xmlns:android="http://schemas.android.com/apk/res/android"
+  xmlns:app="http://schemas.android.com/apk/res-auto"
+  android:layout_width="match_parent"
+  android:layout_height="match_parent"
+  android:fitsSystemWindows="true"
+  android:orientation="vertical">
 
-    <android.support.design.widget.AppBarLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:theme="@style/AppTheme.AppBarOverlay">
+  <android.support.design.widget.AppBarLayout
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:theme="@style/AppTheme.AppBarOverlay">
 
-        <android.support.v7.widget.Toolbar
-            android:id="@+id/toolbar"
-            android:layout_width="match_parent"
-            android:layout_height="?attr/actionBarSize"
-            android:background="?attr/colorPrimary"
-            app:popupTheme="@style/AppTheme.PopupOverlay" />
+    <android.support.v7.widget.Toolbar
+      android:id="@+id/toolbar"
+      android:layout_width="match_parent"
+      android:layout_height="?attr/actionBarSize"
+      android:background="?attr/colorPrimary"
+      app:popupTheme="@style/AppTheme.PopupOverlay"/>
 
-    </android.support.design.widget.AppBarLayout>
+  </android.support.design.widget.AppBarLayout>
 
-    <RelativeLayout
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
+  <RelativeLayout
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
 
-        <include layout="@layout/content_main" />
+    <include layout="@layout/content_main"/>
 
-        <android.support.design.widget.FloatingActionButton
-            android:id="@+id/fab"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_alignParentBottom="true"
-            android:layout_alignParentEnd="true"
-            android:layout_alignParentRight="true"
-            android:layout_margin="@dimen/fab_margin"
-            android:src="@android:drawable/ic_dialog_dialer" />
-    </RelativeLayout>
+    <Button
+      android:id="@+id/fab"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_alignParentBottom="true"
+      android:layout_alignParentEnd="true"
+      android:layout_alignParentRight="true"
+      android:layout_margin="@dimen/fab_margin"
+      android:text="@string/choose_image"/>
+  </RelativeLayout>
 </LinearLayout>

+ 2 - 1
example/src/main/res/values/strings.xml

@@ -1,3 +1,4 @@
 <resources>
-    <string name="app_name">Luban</string>
+  <string name="app_name">Luban</string>
+  <string name="choose_image">选择图片</string>
 </resources>

+ 107 - 0
library/src/main/java/top/zibin/luban/Engine.java

@@ -0,0 +1,107 @@
+package top.zibin.luban;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.media.ExifInterface;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Responsible for starting compress and managing active and cached resources.
+ */
+class Engine {
+  private ExifInterface srcExif;
+  private File srcImg;
+  private File tagImg;
+  private int srcWidth;
+  private int srcHeight;
+
+  Engine(File srcImg, File tagImg) throws IOException {
+    this.srcExif = new ExifInterface(srcImg.getAbsolutePath());
+    this.tagImg = tagImg;
+    this.srcImg = srcImg;
+
+    BitmapFactory.Options options = new BitmapFactory.Options();
+    options.inJustDecodeBounds = true;
+    options.inSampleSize = 1;
+
+    BitmapFactory.decodeFile(srcImg.getAbsolutePath(), options);
+    this.srcWidth = options.outWidth;
+    this.srcHeight = options.outHeight;
+  }
+
+  private int computeSize() {
+    int mSampleSize;
+
+    srcWidth = srcWidth % 2 == 1 ? srcWidth + 1 : srcWidth;
+    srcHeight = srcHeight % 2 == 1 ? srcHeight + 1 : srcHeight;
+
+    srcWidth = srcWidth > srcHeight ? srcHeight : srcWidth;
+    srcHeight = srcWidth > srcHeight ? srcWidth : srcHeight;
+
+    double scale = ((double) srcWidth / srcHeight);
+
+    if (scale <= 1 && scale > 0.5625) {
+      if (srcHeight < 1664) {
+        mSampleSize = 1;
+      } else if (srcHeight >= 1664 && srcHeight < 4990) {
+        mSampleSize = 2;
+      } else if (srcHeight >= 4990 && srcHeight < 10240) {
+        mSampleSize = 4;
+      } else {
+        mSampleSize = srcHeight / 1280 == 0 ? 1 : srcHeight / 1280;
+      }
+    } else if (scale <= 0.5625 && scale > 0.5) {
+      mSampleSize = srcHeight / 1280 == 0 ? 1 : srcHeight / 1280;
+    } else {
+      mSampleSize = (int) Math.ceil(srcHeight / (1280.0 / scale));
+    }
+
+    return mSampleSize;
+  }
+
+  private Bitmap rotatingImage(Bitmap bitmap) {
+    Matrix matrix = new Matrix();
+    int angle = 0;
+    int orientation = srcExif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
+    switch (orientation) {
+      case ExifInterface.ORIENTATION_ROTATE_90:
+        angle = 90;
+        break;
+      case ExifInterface.ORIENTATION_ROTATE_180:
+        angle = 180;
+        break;
+      case ExifInterface.ORIENTATION_ROTATE_270:
+        angle = 270;
+        break;
+    }
+
+    matrix.postRotate(angle);
+
+    return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
+  }
+
+  File compress() throws IOException {
+    BitmapFactory.Options options = new BitmapFactory.Options();
+    options.inSampleSize = computeSize();
+
+    Bitmap tagBitmap = BitmapFactory.decodeFile(srcImg.getAbsolutePath(), options);
+    ByteArrayOutputStream stream = new ByteArrayOutputStream();
+
+    tagBitmap = rotatingImage(tagBitmap);
+    tagBitmap.compress(Bitmap.CompressFormat.JPEG, 50, stream);
+    tagBitmap.recycle();
+
+    FileOutputStream fos = new FileOutputStream(tagImg);
+    fos.write(stream.toByteArray());
+    fos.flush();
+    fos.close();
+    stream.close();
+
+    return tagImg;
+  }
+}

+ 155 - 2
library/src/main/java/top/zibin/luban/Luban.java

@@ -1,8 +1,161 @@
 package top.zibin.luban;
 
-public class Luban {
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.support.annotation.Nullable;
+import android.support.annotation.UiThread;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+
+public class Luban implements Handler.Callback {
   private static final String TAG = "Luban";
   private static final String DEFAULT_DISK_CACHE_DIR = "luban_disk_cache";
 
+  private static final int MSG_COMPRESS_SUCCESS = 0;
+  private static final int MSG_COMPRESS_START = 1;
+  private static final int MSG_COMPRESS_ERROR = 2;
+
+  private File file;
+  private OnCompressListener onCompressListener;
+
+  private Handler mHandler;
+
+  private Luban(Builder builder) {
+    this.file = builder.file;
+    this.onCompressListener = builder.onCompressListener;
+    mHandler = new Handler(Looper.getMainLooper(), this);
+  }
+
+  public static Builder get(Context context) {
+    return new Builder(context);
+  }
+
+  /**
+   * Returns a file with a cache audio name in the private cache directory.
+   *
+   * @param context
+   *     A context.
+   */
+  private File getImageCacheFile(Context context) {
+    if (getImageCacheDir(context) != null) {
+      return new File(getImageCacheDir(context) + "/" + System.currentTimeMillis());
+    }
+    return null;
+  }
+
+  /**
+   * Returns a directory with a default name in the private cache directory of the application to
+   * use to store retrieved audio.
+   *
+   * @param context
+   *     A context.
+   *
+   * @see #getImageCacheDir(android.content.Context, String)
+   */
+  @Nullable
+  private File getImageCacheDir(Context context) {
+    return getImageCacheDir(context, DEFAULT_DISK_CACHE_DIR);
+  }
+
+  /**
+   * Returns a directory with the given name in the private cache directory of the application to
+   * use to store retrieved media and thumbnails.
+   *
+   * @param context
+   *     A context.
+   * @param cacheName
+   *     The name of the subdirectory in which to store the cache.
+   *
+   * @see #getImageCacheDir(android.content.Context)
+   */
+  @Nullable
+  private File getImageCacheDir(Context context, String cacheName) {
+    File cacheDir = context.getExternalCacheDir();
+    if (cacheDir != null) {
+      File result = new File(cacheDir, cacheName);
+      if (!result.mkdirs() && (!result.exists() || !result.isDirectory())) {
+        // File wasn't able to create a directory, or the result exists but not a directory
+        return null;
+      }
+      return result;
+    }
+    if (Log.isLoggable(TAG, Log.ERROR)) {
+      Log.e(TAG, "default disk cache dir is null");
+    }
+    return null;
+  }
+
+  @UiThread private void launch(final Context context) {
+    if (file == null && onCompressListener != null) {
+      onCompressListener.onError(new NullPointerException("image file cannot be null"));
+    }
+
+    new Thread(new Runnable() {
+      @Override public void run() {
+        try {
+          mHandler.sendMessage(mHandler.obtainMessage(MSG_COMPRESS_START));
+
+          File result = new Engine(file, getImageCacheFile(context)).compress();
+          mHandler.sendMessage(mHandler.obtainMessage(MSG_COMPRESS_SUCCESS, result));
+        } catch (IOException e) {
+          mHandler.sendMessage(mHandler.obtainMessage(MSG_COMPRESS_ERROR, e));
+        }
+      }
+    }).start();
+  }
+
+  @Override public boolean handleMessage(Message msg) {
+    if (onCompressListener == null) return false;
+
+    switch (msg.what) {
+      case MSG_COMPRESS_START:
+        onCompressListener.onStart();
+        break;
+      case MSG_COMPRESS_SUCCESS:
+        onCompressListener.onSuccess((File) msg.obj);
+        break;
+      case MSG_COMPRESS_ERROR:
+        onCompressListener.onError((Throwable) msg.obj);
+        break;
+    }
+    return false;
+  }
+
+  public static class Builder {
+    private Context context;
+    private File file;
+    private OnCompressListener onCompressListener;
+
+    Builder(Context context) {
+      this.context = context;
+    }
+
+    private Luban build() {
+      return new Luban(this);
+    }
+
+    public Builder load(File file) {
+      this.file = file;
+      return this;
+    }
+
+    public Builder putGear(int gear) {
+      return this;
+    }
+
+    public Builder setCompressListener(OnCompressListener listener) {
+      this.onCompressListener = listener;
+      return this;
+    }
 
-}
+    public Luban launch() {
+      Luban luban = build();
+      luban.launch(context);
+      return luban;
+    }
+  }
+}

+ 0 - 5
library/src/main/java/top/zibin/luban/LubanOld.java

@@ -20,8 +20,6 @@ import rx.functions.Action1;
 import rx.functions.Func1;
 import rx.schedulers.Schedulers;
 
-import static top.zibin.luban.Preconditions.checkNotNull;
-
 public class LubanOld {
 
   private static final int FIRST_GEAR = 1;
@@ -97,7 +95,6 @@ public class LubanOld {
   }
 
   public LubanOld launch() {
-    checkNotNull(mFile, "the image file cannot be null, please call .load() before this method!");
 
     if (compressListener != null) compressListener.onStart();
 
@@ -458,8 +455,6 @@ public class LubanOld {
    *     the file size of image   期望大小
    */
   private File saveImage(String filePath, Bitmap bitmap, long size) {
-    checkNotNull(bitmap, TAG + "bitmap cannot be null");
-
     File result = new File(filePath.substring(0, filePath.lastIndexOf("/")));
 
     if (!result.exists() && !result.mkdirs()) return null;

+ 12 - 12
library/src/main/java/top/zibin/luban/OnCompressListener.java

@@ -4,18 +4,18 @@ import java.io.File;
 
 public interface OnCompressListener {
 
-    /**
-     * Fired when the compression is started, override to handle in your own code
-     */
-    void onStart();
+  /**
+   * Fired when the compression is started, override to handle in your own code
+   */
+  void onStart();
 
-    /**
-     * Fired when a compression returns successfully, override to handle in your own code
-     */
-    void onSuccess(File file);
+  /**
+   * Fired when a compression returns successfully, override to handle in your own code
+   */
+  void onSuccess(File file);
 
-    /**
-     * Fired when a compression fails to complete, override to handle in your own code
-     */
-    void onError(Throwable e);
+  /**
+   * Fired when a compression fails to complete, override to handle in your own code
+   */
+  void onError(Throwable e);
 }

+ 0 - 36
library/src/main/java/top/zibin/luban/Preconditions.java

@@ -1,36 +0,0 @@
-package top.zibin.luban;
-
-import android.support.annotation.Nullable;
-
-final class Preconditions {
-
-    /**
-     * Ensures that an object reference passed as a parameter to the calling method is not null.
-     *
-     * @param reference an object reference
-     * @return the non-null reference that was validated
-     * @throws NullPointerException if {@code reference} is null
-     */
-    static <T> T checkNotNull(T reference) {
-        if (reference == null) {
-            throw new NullPointerException();
-        }
-        return reference;
-    }
-
-    /**
-     * Ensures that an object reference passed as a parameter to the calling method is not null.
-     *
-     * @param reference    an object reference
-     * @param errorMessage the exception message to use if the check fails; will be converted to a
-     *                     string using {@link String#valueOf(Object)}
-     * @return the non-null reference that was validated
-     * @throws NullPointerException if {@code reference} is null
-     */
-    static <T> T checkNotNull(T reference, @Nullable Object errorMessage) {
-        if (reference == null) {
-            throw new NullPointerException(String.valueOf(errorMessage));
-        }
-        return reference;
-    }
-}