Android圖片系列-2.Android App圖片壓縮、裁剪分析整理

移動端常用的圖片格式有PNG和JPEG,目前ios手機和大部分安卓手機拍照生成的圖片默認格式都是JPEG。我們開發APP的時候通常使用的是PNG,這可能是考慮到圖片質量效果。PNG圖片是無損壓縮格式,JPEG是有損壓縮,PNG格式相比JPEG佔用空間要大很多。2010年GOOGLE開發了一種新的圖片壓縮格式WEBP,可以將圖片空間壓縮的更小,平均是JPEG的2/3,Android從4.0開始默認支持WEBP格式。關於JPEG、PNG網上有很多介紹,本文主要介紹自己在實際開發中遇到問題處理方法的一些總結經驗。

一 圖片格式

在電腦中,JPEG(發音爲jay-peg, IPA:[ˈdʒeɪpɛg])是一種針對照片視頻而廣泛使用的一種有損壓縮標準方法。這個名稱代表Joint Photographic Experts Group(聯合圖像專家小組)。此團隊創立於1986年,1992年發佈了JPEG的標準而在1994年獲得了ISO 10918-1的認定。JPEG與視頻音頻壓縮標準的MPEG(Moving Picture Experts Group)很容易混淆,但兩者是不同的組織及標準。
JPEG格式介紹

便攜式網絡圖形(Portable Network Graphics,PNG)是一種無損壓縮的位圖圖形格式,支持索引、灰度、RGB三種顏色方案以及Alpha通道等特性。PNG的開發目標是改善並取代GIF作爲適合網絡傳輸的格式而不需專利許可,所以被廣泛應用於互聯網及其他方面上。
PNG格式介紹

二 圖片壓縮

1.1 壓圖片

壓圖片是指圖片佔用存儲空間和圖片質量的壓。記得以前做手機rom的時候,爲了縮減app包的體積,我們會使用工具比如tinypng對UI工程師給的PNG圖片進行壓縮,平均下來可以壓縮百分之三四十,這與圖片本身的色彩有關係。

IOS壓圖片

UIImageJPEGRepresentation壓縮圖片,壓縮率用1圖片處理後佔用的存儲空間會變大,使用0.8的時候大小與圖片原來存儲大小接近。看到一些分析,圖片壓縮率根據場景需要自己來配置,建議參考範圍是0.6~1。如果壓縮率太小,圖片質量會非常糟糕。開始使用阿里雲OSS存儲的時候發現這個問題,以爲是阿里SDK造成的,還給阿里提了一個工單,前後溝通了一個多月,阿里的售後堅持說他們的SDK沒有這個問題,最終不了了之。

壓縮次數 圖片大小
0 1M
1 4M
2 4.8M
3 5M

表1.1.1 UIImageJPEGRepresentation使用壓縮率1處理圖片

ANDROID壓圖片

Android 使用Bitmap來處理圖片,先來看下bitmap的壓縮方法compress,代碼如下:

//Bitmap.java
/** 
    * Write a compressed version of the bitmap to the specified outputstream. 
    * If this returns true, the bitmap can be reconstructed by passing a 
    * corresponding inputstream to BitmapFactory.decodeStream(). Note: not 
    * all Formats support all bitmap configs directly, so it is possible that 
    * the returned bitmap from BitmapFactory could be in a different bitdepth, 
    * and/or may have lost per-pixel alpha (e.g. JPEG only supports opaque 
    * pixels). 
    * 
    * @param format   The format of the compressed image 
    * @param quality  Hint to the compressor, 0-100. 0 meaning compress for 
    *                 small size, 100 meaning compress for max quality. Some 
    *                 formats, like PNG which is lossless, will ignore the 
    *                 quality setting 
    * @param stream   The outputstream to write the compressed data. 
    * @return true if successfully compressed to the specified stream. 
*/
compress(CompressFormat format, int quality, OutputStream stream)
  1. CompressFormat支持三種格式PNG、JPG、WEBP
  2. quality 取值範圍0~100,100代表最小壓縮,圖片保存質量最高。
  3. 經過實測,將PNG或者JPG圖片壓縮成WEBP非常耗時,對於實時處理需求的場景不適合通過此方法來達到減小圖片佔用空間的目的。

另外,Bitmap還提供了一個內部類Config

 /**
     * Possible bitmap configurations. A bitmap configuration describes
     * how pixels are stored. This affects the quality (color depth) as
     * well as the ability to display transparent/translucent colors.
     */
    public enum Config {
        // these native values must match up with the enum in SkBitmap.h

        /**
         * Each pixel is stored as a single translucency (alpha) channel.
         * This is very useful to efficiently store masks for instance.
         * No color information is stored.
         * With this configuration, each pixel requires 1 byte of memory.
         */
        ALPHA_8     (2),

        /**
         * Each pixel is stored on 2 bytes and only the RGB channels are
         * encoded: red is stored with 5 bits of precision (32 possible
         * values), green is stored with 6 bits of precision (64 possible
         * values) and blue is stored with 5 bits of precision.
         * 
         * This configuration can produce slight visual artifacts depending
         * on the configuration of the source. For instance, without
         * dithering, the result might show a greenish tint. To get better
         * results dithering should be applied.
         * 
         * This configuration may be useful when using opaque bitmaps
         * that do not require high color fidelity.
         */
        RGB_565     (4),

        /**
         * Each pixel is stored on 2 bytes. The three RGB color channels
         * and the alpha channel (translucency) are stored with a 4 bits
         * precision (16 possible values.)
         * 
         * This configuration is mostly useful if the application needs
         * to store translucency information but also needs to save
         * memory.
         * 
         * It is recommended to use {@link #ARGB_8888} instead of this
         * configuration.
         *
         * Note: as of {@link android.os.Build.VERSION_CODES#KITKAT},
         * any bitmap created with this configuration will be created
         * using {@link #ARGB_8888} instead.
         * 
         * @deprecated Because of the poor quality of this configuration,
         *             it is advised to use {@link #ARGB_8888} instead.
         */
        @Deprecated
        ARGB_4444   (5),

        /**
         * Each pixel is stored on 4 bytes. Each channel (RGB and alpha
         * for translucency) is stored with 8 bits of precision (256
         * possible values.)
         * 
         * This configuration is very flexible and offers the best
         * quality. It should be used whenever possible.
         */
        ARGB_8888   (6);

        final int nativeInt;

        @SuppressWarnings({"deprecation"})
        private static Config sConfigs[] = {
            null, null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888
        };

        Config(int ni) {
            this.nativeInt = ni;
        }

        static Config nativeToConfig(int ni) {
            return sConfigs[ni];
        }
    }

從代碼看到bitmap包括ALPHA_8(8位)、ALPHA_565(16位)、ALPHA_4444(16位)、ALPHA_8888(32位)四種配置色彩。bitmap是一種使用像素點來表達圖片的存儲方式,一張圖片分辨率是3776 * 2520,圖片色彩模式是ARGB 32位,那麼圖片加載的時候消耗的內存空間:3776 * 2520 * 4byte = 38062080byte = 38M。bitmap是非壓縮存儲格式,因此佔用內存空間會非常大。所以通過不同的色彩配置也可以達到圖片壓縮的效果,比如選擇ALPHA_565(16位),圖片佔用內存空間就會比32位減小一半,保存成jpg格式的時候也會小。

1.2 圖片縮

圖片縮是指圖片分辨率的裁剪。圖片的裁剪使用場景很多:
1. listview ,recycleview加載圖片列表,此時應該使用縮略圖。當點擊查看item的時候才呈現大圖。
2. 假設移動設備分辨率只有1024X600,設備端應用分配的堆內存是64M。存儲的圖片分辨率是3776 * 2520,圖片色彩模式是ARGB 32位,那麼圖片加載的時候消耗的內存空間38M。這個數值已經快接近該應用能夠分配的最大內存空間了,如果圖片分辨率再高一些,就會直接報內存溢出的錯誤提示。因此在加載之前,我們可能需要針對設備配置對圖片進行裁剪。在下文會談到的開源圖片加載庫Android Universal Imageloader裏面就做了類似處理,代碼裏面有限制加載的圖片分辨率如果長或者寬大於等於2048,就會把這個圖片分辨率減小。

/**
 * UIL 圖片裁剪
 */
public final class ImageSizeUtils {
    //設置最大值爲2048,超過這個值就會將分辨率縮小
    private static final int DEFAULT_MAX_BITMAP_DIMENSION = 2048;

    private static ImageSize maxBitmapSize;

    static {
        int[] maxTextureSize = new int[1];
        GLES10.glGetIntegerv(GL10.GL_MAX_TEXTURE_SIZE, maxTextureSize, 0);
        int maxBitmapDimension = Math.max(maxTextureSize[0], DEFAULT_MAX_BITMAP_DIMENSION);
        maxBitmapSize = new ImageSize(maxBitmapDimension, maxBitmapDimension);
    }

...

//ImageLoader.java
//可以使用UIL加載指定大小的圖片,具體的參數由ImageSize來控制。
    public void loadImage(String uri, ImageSize targetImageSize, ImageLoadingListener listener) {
        loadImage(uri, targetImageSize, null, listener, null);
    }

3 . 比如我們做的電子相框,顯示圖片的時候由於圖片的分辨率長寬比與相框設備分辨率長寬比不一樣,會導致圖片無法全屏展示。如果我們想要實現全屏顯示圖片,就需要對圖片進行裁剪,裁剪思路就是將圖片裁成一個長寬比與設備長寬比一致。如果是針對豎屏顯示的設備,方法是相通的。

//將圖片裁剪成與設備長寬比一致。裁剪方法是針對橫屏顯示設備只裁剪寬大於等於高的圖片,因爲如果對高大於寬的圖片也裁剪,會使得圖片內容變化較大。因此,我們這裏的裁剪算是一種優化,比如一張4:3的圖要顯示在16:9的設備上,下面的處理就可以將圖片優化成全屏顯示。
public Bitmap imageCrop(Bitmap bitmap) {
        boolean deviceLand = DEVICE_WIDTH >DEVICE_HEIGHT;
        float w = bitmap.getWidth(); // 得到圖片的寬,高
        float h = bitmap.getHeight();
        boolean bitmapLand = w>=h;
        float retX = 0;
        float retY = 0;
        //先處理橫屏設備,橫屏圖片
        if(deviceLand && bitmapLand){
            float deviceRatio = Constants.DEVICE_WIDTH / Constants.DEVICE_HEIGHT;
            float bitmapRatio = w / h ;
            if (bitmapRatio > deviceRatio){
                float cropWidth = h * deviceRatio;
                retX = (w - (int)cropWidth)/2;
                Bitmap cropBitmap = Bitmap.createBitmap(bitmap, (int)retX, (int)retY, (int)cropWidth, (int)h, null, false);
                return cropBitmap;
            }else {
                float cropHeight = w / deviceRatio;
                retY = (h - (int)cropHeight)/2;
                Bitmap cropBitmap = Bitmap.createBitmap(bitmap, (int)retX, (int)retY, (int)w, (int)cropHeight, null, false);
                return cropBitmap;
            }
        }else if(!deviceLand && !bitmapLand){

        }
        return bitmap;
    }

安卓開源有一個很不錯的圖片裁剪控件,是知名公司yalantis的作品。ucrop圖片裁剪庫

三 圖片格式轉換

既然WEBP格式最小,那麼在對圖片清晰度沒有非常高的要求下,爲了節省帶寬流量,移動端可以在上傳的時候將PNG和JPEG格式的圖片轉換爲WEBP,但是實際使用過程發現在移動端轉換成WEBP格式圖片的耗時非常久,體驗不好,因此如果這種方式並不能通用。服務器也可以在下發給移動端圖片的時候將PNG和JPEG格式的圖片轉換爲WEBP,目前阿里雲已經開通了這個服務,不過是收費的,但是費用相比因爲流量減小節省的成本還是非常划算的。

阿里雲圖片處理服務 提供了圖片格式轉換、圖片裁剪、圖片旋轉、圖片效果等在線api服務。

四 圖片的方向

現在蘋果手機、一些安卓手機自帶的相機應用,拍照的時候不管手機方向怎麼旋轉,拍出來的圖片用相冊預覽的時候發現都是正的。使用數碼相機或者手機倒着拍一些照片,然後將圖片導入windows7系統,可以看到倒着的圖片,導入windows10系統圖片預覽的時候全是正的。這說明圖片內部存儲了方向這個信息,圖片瀏覽器在展示圖片的時候可以選擇是否依據圖片方向的值對圖片進行校正。

可交換圖像文件格式常被簡稱爲Exif(Exchangeable image file format),是專門爲數碼相機的照片設定的,可以記錄數碼照片的屬性信息和拍攝數據。
Exif最初由日本電子工業發展協會在1996年制定,版本爲1.0。1998年,升級到2.1,增加了對音頻文件的支持。2002年3月,發表了2.2版。
Exif可以附加於JPEG、TIFF、RIFF等文件之中,爲其增加有關數碼相機拍攝信息的內容和索引圖或圖像處理軟件的版本信息。
Windows 7操作系統具備對Exif的原生支持,通過鼠標右鍵點擊圖片打開菜單,點擊屬性並切換到詳細信息標籤下即可直接查看Exif信息。
Exif信息是可以被任意編輯的,因此只有參考的功能。
Exif信息以0xFFE1作爲開頭標記,後兩個字節表示Exif信息的長度。所以Exif信息最大爲64 kB,而內部採用TIFF格式
WIKI

//Android讀取圖片方向信息
public int readPictureDegree(String imagePath) {
        int imageDegree = 0;
        try {
            ExifInterface exifInterface = new ExifInterface(imagePath);
            int orientation = exifInterface.getAttributeInt(
                    ExifInterface.TAG_ORIENTATION,
                    ExifInterface.ORIENTATION_NORMAL);
            switch (orientation) {
            case ExifInterface.ORIENTATION_ROTATE_90:
                imageDegree = 90;
                break;
            case ExifInterface.ORIENTATION_ROTATE_180:
                imageDegree = 180;
                break;
            case ExifInterface.ORIENTATION_ROTATE_270:
                imageDegree = 270;
                break;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return imageDegree;
    }

    //根據圖片方向信息旋轉圖片
    public Bitmap rotaingImageView(int angle, Bitmap mBitmap) {
        Matrix matrix = new Matrix();
        matrix.postRotate(angle);
        Bitmap b = Bitmap.createBitmap(mBitmap, 0, 0, mBitmap.getWidth(),
                mBitmap.getHeight(), matrix, true);
        return b;
    }

五 安卓開源的圖片加載框架

安卓圖片處理框架,目前我只用過UIL。最近準備把另外三個框架學習一下,畢竟UIL已經停止維護了,要跟着新技術的腳步,是時候切換一下新的生產力工具了。畢竟從說明來看,fresco在內存OOM問題上表現的更爲出色。拭目以待。

Memory
A decompressed image - an Android Bitmap - takes up a lot of memory. This leads to more frequent runs of the Java garbage collector. This slows apps down. The problem is especially bad without the improvements to the garbage collector made in Android 5.0.
On Android 4.x and lower, Fresco puts images in a special region of Android memory. It also makes sure that images are automatically released from memory when they’re no longer shown on screen. This lets your application run faster - and suffer fewer crashes.
Apps using Fresco can run even on low-end devices without having to constantly struggle to keep their image memory footprint under control.

圖片系列鏈接:Android圖片系列-1.如何選擇圖片加載框架

  1. UIL Universal ImageLoader
  2. Picasso
  3. Glide
  4. Fresco

參考:

  1. 阿里雲圖片處理服務
  2. EXIF WIKI文檔
  3. JPEG格式介紹
  4. PNG格式介紹
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章