为了账号安全,请及时绑定邮箱和手机立即绑定

ThumbnailUtils工具类整理

标签:
Android

前言

[代码]java代码:

?

1

2

3

4

5

//   frameworks/base/core/java/android/provider/MediaStoreSaver.java

// Images.Thumbnails

public static final int MINI_KIND = 1; // 512 x 384

public static final int FULL_SCREEN_KIND = 2; // 未定义

public static final int MICRO_KIND = 3; // 160 * 120



特别喜欢系统中一些小而精的工具类,有的时候分析一下别有一番味道。
ThumbnailUtils是系统内置的一个生成缩略图的工具类,只有512行代码,网上有很多使用ThumbnailUtils的例子,刚好我个人正在整理Bitmap的相关资料,希望从中也能有所收获。

几个概念

像素规范

系统中对缩略图的像素定义了三种规范:

 

对于开发者,只支持MINI_KIND和MICRO_KIND两种类型。为什么是这个像素呢?因为ThumbnailUtils中定义如下:

[代码]java代码:

?

1

2

3

4

5

6

public class ThumbnailUtils   {

    /* Maximum pixels   size for created bitmap. */

    private static final int MAX_NUM_PIXELS_THUMBNAIL = 512 * 384;

    private static final int MAX_NUM_PIXELS_MICRO_THUMBNAIL = 160 * 120;

    private static final int UNCONSTRAINED = -1;

}

 

其中MAX_NUM_PIXELS_MICRO_THUMBNAIL的值之前是128 128,在4.2+版本上被调整为160 120,原因很简单,现在手机拍摄照片比例普遍是4:3,如果不是这个比例生成缩略图的时候需要更多的计算。

尺寸规范

系统中对MINI_KIND和MICRO_KIND两种类型的图片尺寸做了限制,强调一下,是“系统”。

[代码]java代码:

?

1

2

public static final int TARGET_SIZE_MINI_THUMBNAIL = 320;

public static final int TARGET_SIZE_MICRO_THUMBNAIL = 96;

 

当然这两个字段是@hide的,是专门系统用的。
如果图片缩略图,MINI_KIND则等比例缩到360,MICRO_KIND则缩放为96 x 96的正方形(实现方法参考下面的#最合适的缩略图)
如果视频缩略图,MINI_KIND则等比例缩到512(这个512是写死在代码里的magic number),MICRO_KIND则缩放为96 x 96的正方形(实现方法参考下面的#最合适的缩略图)

Exif格式

Exif是一种图像文件格式,它的数据存储与JPEG格式是完全相同的。实际上Exif格式就是在JPEG格式头部插入了数码照片的信息,包括拍摄时的光圈、快门、白平衡、ISO、焦距、日期时间等各种和拍摄条件以及相机品牌、型号、色彩编码、拍摄时录制的声音以及GPS全球定位系统数据、缩略图等。

具体元信息,可参考f/b/media/java/android/media/ExifInterface.java
这里我特别指出ExifInterface的两点,在大家工作中很有可能会碰到:

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

13

/**

 * This is a class for reading and   writing Exif tags in a JPEG file.

 */

public class ExifInterface   {

    // 1. 方向,也就是旋转角度

    public static final String   TAG_ORIENTATION = "Orientation";

    // 2. 从Exif中获取缩略图,   如果没有则返回null

    public byte[] getThumbnail() {

        synchronized (sLock) {

            return getThumbnailNative(mFilename);

        }

    }

}

 

最合适的缩略图

等比例缩放只需要按Bitmap.createBitmap即可,但是Thumbnail的缩略图生成算法中为了从中间截图最合适的部分,包含了裁剪的逻辑。主要分两步:

1.     先缩放:按照填满的思想缩放到目标大小

2.     再裁剪:从中间裁剪目标大小的区域

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

/**

 * 把原始图片转化为目标大小的图片,从中间截图

 * 注意:这里我把放大的一个逻辑处理删除了,那段逻辑永远不会执行

 */

private static Bitmap   transform(Matrix scaler,

        Bitmap   source,

        int targetWidth,

        int targetHeight,

        int options) {

    // 是否回收原始Bitmap

    boolean recycle = (options &   OPTIONS_RECYCLE_INPUT) != 0;

    // 计算是按宽度还是高度计算缩放比例

    // 这里通过高宽比计算缩放的方法,可以用填满的思维去想象一下

    float bitmapWidthF = source.getWidth();

    float bitmapHeightF = source.getHeight();

    float bitmapAspect = bitmapWidthF /   bitmapHeightF;

    float viewAspect   = (float)   targetWidth / targetHeight;

    if (bitmapAspect > viewAspect) {

        float scale = targetHeight / bitmapHeightF;

        if (scale < .9F || scale > 1F) {

            scaler.setScale(scale,   scale);

        }   else {

            scaler   = null;

        }

    } else {

        float scale = targetWidth / bitmapWidthF;

        if (scale < .9F || scale > 1F) {

            scaler.setScale(scale,   scale);

        }   else {

            scaler   = null;

        }

    }

    // 调用Bitmap.createBitmap方法按上面算出的缩放比例等比例缩小

    Bitmap b1;

    if (scaler != null) {

        //   this is used for minithumb and crop, so we want to filter here.

        b1   = Bitmap.createBitmap(source, 0, 0,

                source.getWidth(),   source.getHeight(), scaler, true);

    } else {

        b1   = source;

    }

    if (recycle && b1 != source) {

        source.recycle();

    }

    // 从中间裁剪最合适部分

    int dx1 = Math.max(0, b1.getWidth() -   targetWidth);

    int dy1 = Math.max(0, b1.getHeight() -   targetHeight);

    Bitmap b2 =   Bitmap.createBitmap(

            b1,

            dx1   / 2,

            dy1   / 2,

            targetWidth,

            targetHeight);

    if (b2 != b1) {

        if (recycle || b1 != source) {

            b1.recycle();

        }

    }

    return b2;

}

 

基于上面的算法,ThumbnailUtils对外提供了如下接口生成缩略图:

[代码]java代码:

?

1

2

3

// options主要用于是否回收原始Bitmap

public static Bitmap   extractThumbnail(Bitmap source, int width, int height, int options)

public static Bitmap   extractThumbnail(Bitmap source, int width, int height)

 

视频缩略图

使用MediaMetadataRetriever读取视频第一帧Bitmap,然后据此再生成缩略图。
如果kind为Thumbnails.MINI_KIND,就等比例生成最大宽或者高为512的小图。
如果king为Thumbnails.MICRO_KIND,就使用上面讲的最合适的缩略图算法,生成96 x 96的正方形小图

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

public static Bitmap   createVideoThumbnail(String filePath, int kind) {

    Bitmap bitmap = null;

    MediaMetadataRetriever   retriever = new MediaMetadataRetriever();

    try {

        retriever.setDataSource(filePath);

        bitmap   = retriever.getFrameAtTime(-1);

    } catch (IllegalArgumentException ex) {

        //   Assume this is a corrupt video file

    } catch (RuntimeException ex) {

        //   Assume this is a corrupt video file.

    } finally {

        try {

            retriever.release();

        }   catch (RuntimeException   ex) {

            //   Ignore failures while cleaning up.

        }

    }

    if (bitmap == null) return null;

    if (kind == Images.Thumbnails.MINI_KIND) {

        //   Scale down the bitmap if it's too large.

        int width = bitmap.getWidth();

        int height = bitmap.getHeight();

        int max = Math.max(width, height);

        if (max > 512) {

            float scale = 512f / max;

            int w = Math.round(scale * width);

            int h = Math.round(scale * height);

            bitmap   = Bitmap.createScaledBitmap(bitmap, w, h, true);

        }

    } else if (kind == Images.Thumbnails.MICRO_KIND) {

        bitmap   = extractThumbnail(bitmap,

                TARGET_SIZE_MICRO_THUMBNAIL,

                TARGET_SIZE_MICRO_THUMBNAIL,

                OPTIONS_RECYCLE_INPUT);

    }

    return bitmap;

}

 

内部方法

ThumbnailUtils其实对外的方法就上面三个演示的三个方法,除此之外,内部还有两部分,一部分是生成图片文件的缩略图,另外一部分就是未使用的无用代码。

计算SampleSize

系统中新加入一张图,就要生成缩略图了,最重要的就是计算SampleSize了,ThumbnailUtils提供了两种算法:

按目标最小边(minSideLength)

定义最小边的缩放比例
(int) Math.min(Math.floor(w / minSideLength), Math.floor(h / minSideLength))

按目标像素(maxNumOfPixels)

定义像素的缩放比例
(int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels))

具体实现

同时支持不指定限制,也做了一个默认值处理,实现如下:

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

// 计算缩放比例

private static int computeInitialSampleSize(BitmapFactory.Options   options,

        int minSideLength, int maxNumOfPixels) {

    double w = options.outWidth;

    double h = options.outHeight;

    int lowerBound = (maxNumOfPixels ==   UNCONSTRAINED) ? 1 :

        (int)   Math.ceil(Math.sqrt(w * h / maxNumOfPixels));

    int upperBound = (minSideLength ==   UNCONSTRAINED) ? 128 :

        (int)   Math.min(Math.floor(w / minSideLength),

                Math.floor(h   / minSideLength));

    if (upperBound < lowerBound) {

        //   return the larger one when there is no overlapping zone.

        return lowerBound;

    }

    if ((maxNumOfPixels == UNCONSTRAINED)   &&

            (minSideLength   == UNCONSTRAINED)) {

        return 1;

    } else if (minSideLength == UNCONSTRAINED) {

        return lowerBound;

    } else {

        return upperBound;

    }

}

 

但是上面的缩放比例不是标准的2的次放,不符合BitmapFactory的规范,再封装一下:

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

// 规范化上面的sampleSize为2的次方或者8的倍数

// 据说这是BitmapFactory的要求,可以避免OOM?注释里说的。

private static int computeSampleSize(BitmapFactory.Options   options,

        int minSideLength, int maxNumOfPixels) {

    int initialSize =   computeInitialSampleSize(options, minSideLength,

            maxNumOfPixels);

    int roundedSize;

    if (initialSize <= 8) {

        //   如果小于8,转化为2的次方(通过位移来转化,可以借鉴一下)

        roundedSize   = 1;

        while (roundedSize < initialSize) {

            roundedSize   <<= 1;

        }

    } else {

        //   如果大于8,转化为8的倍数

        roundedSize   = (initialSize + 7) / 8 * 8;

    }

    return roundedSize;

}

 

从EXIF中选取缩略图

只支持JPG中读取EXIF信息。
这里不是说EXIF有缩略图就用这个缩略图,而是会先用高宽算出文件本身的TargetSize对应的缩略图,和EXIF中缩放到TargetSize对应的缩略图比较,哪个大取哪个。

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

/**

 * Creates a bitmap by either   downsampling from the thumbnail in EXIF or the full image.

 * The functions returns a   SizedThumbnailBitmap,

 * which contains a downsampled   bitmap and the thumbnail data in EXIF if exists.

 */

private static void createThumbnailFromEXIF(String filePath, int targetSize,

        int maxPixels, SizedThumbnailBitmap   sizedThumbBitmap) {

    if (filePath == null) return;

    ExifInterface   exif = null;

    byte [] thumbData = null;

    try {

        exif   = new ExifInterface(filePath);

        thumbData   = exif.getThumbnail();

    } catch (IOException ex) {

        Log.w(TAG,   ex);

    }

    BitmapFactory.Options   fullOptions = new BitmapFactory.Options();

    BitmapFactory.Options   exifOptions = new BitmapFactory.Options();

    int exifThumbWidth = 0;

    int fullThumbWidth = 0;

    // Compute   exifThumbWidth.

    if (thumbData != null) {

        exifOptions.inJustDecodeBounds   = true;

        BitmapFactory.decodeByteArray(thumbData,   0, thumbData.length, exifOptions);

        exifOptions.inSampleSize   = computeSampleSize(exifOptions, targetSize, maxPixels);

        exifThumbWidth   = exifOptions.outWidth / exifOptions.inSampleSize;

    }

    // Compute   fullThumbWidth.

    fullOptions.inJustDecodeBounds   = true;

    BitmapFactory.decodeFile(filePath,   fullOptions);

    fullOptions.inSampleSize   = computeSampleSize(fullOptions, targetSize, maxPixels);

    fullThumbWidth =   fullOptions.outWidth / fullOptions.inSampleSize;

    // Choose the   larger thumbnail as the returning sizedThumbBitmap.

    if (thumbData != null && exifThumbWidth >=   fullThumbWidth) {

        int width = exifOptions.outWidth;

        int height = exifOptions.outHeight;

        exifOptions.inJustDecodeBounds   = false;

        sizedThumbBitmap.mBitmap   = BitmapFactory.decodeByteArray(thumbData, 0,

                thumbData.length,   exifOptions);

        if (sizedThumbBitmap.mBitmap != null) {

            sizedThumbBitmap.mThumbnailData   = thumbData;

            sizedThumbBitmap.mThumbnailWidth   = width;

            sizedThumbBitmap.mThumbnailHeight   = height;

        }

    } else {

        fullOptions.inJustDecodeBounds   = false;

        sizedThumbBitmap.mBitmap   = BitmapFactory.decodeFile(filePath, fullOptions);

    }

}

 

图片文件缩略图

如果是MINI_KIND,尺寸最小边缩放到320左右,像素缩放到512 x 387。否则就是MICRO_KIND,尺寸最大边缩放到96,像素所放到160 x 120。
如果图片是JPG,参考上面的方法从EXIF中选取缩略图。否则,用decodeFileDescriptor()老老实实等比例生成缩略图。
最终成功后,如果是MICRO_KIND,还要裁剪为96 x 96的正方形。

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

public static Bitmap   createImageThumbnail(String filePath, int kind) {

    boolean wantMini = (kind ==   Images.Thumbnails.MINI_KIND);

    int targetSize = wantMini

        ?   TARGET_SIZE_MINI_THUMBNAIL

        :   TARGET_SIZE_MICRO_THUMBNAIL;

    int maxPixels = wantMini

        ?   MAX_NUM_PIXELS_THUMBNAIL

        :   MAX_NUM_PIXELS_MICRO_THUMBNAIL;

    SizedThumbnailBitmap   sizedThumbnailBitmap = new SizedThumbnailBitmap();

    Bitmap bitmap = null;

    MediaFileType   fileType = MediaFile.getFileType(filePath);

    if (fileType != null && fileType.fileType ==   MediaFile.FILE_TYPE_JPEG) {

        createThumbnailFromEXIF(filePath,   targetSize, maxPixels, sizedThumbnailBitmap);

        bitmap   = sizedThumbnailBitmap.mBitmap;

    }

    if (bitmap == null) {

        FileInputStream   stream = null;

        try {

            stream   = new FileInputStream(filePath);

            FileDescriptor   fd = stream.getFD();

            BitmapFactory.Options   options = new BitmapFactory.Options();

            options.inSampleSize   = 1;

            options.inJustDecodeBounds   = true;

            BitmapFactory.decodeFileDescriptor(fd,   null, options);

            if (options.mCancel || options.outWidth == -1

                    ||   options.outHeight == -1) {

                return null;

            }

            options.inSampleSize   = computeSampleSize(

                    options,   targetSize, maxPixels);

            options.inJustDecodeBounds   = false;

            options.inDither   = false;

            options.inPreferredConfig   = Bitmap.Config.ARGB_8888;

            bitmap   = BitmapFactory.decodeFileDescriptor(fd, null, options);

        }   catch (IOException   ex) {

            Log.e(TAG,   "", ex);

        }   catch (OutOfMemoryError   oom) {

            Log.e(TAG,   "Unable to decode file " + filePath + ". OutOfMemoryError.", oom);

        }   finally {

            try {

                if (stream != null) {

                    stream.close();

                }

            }   catch (IOException   ex) {

                Log.e(TAG,   "", ex);

            }

        }

    }

    if (kind == Images.Thumbnails.MICRO_KIND) {

        //   now we make it a "square thumbnail" for MICRO_KIND thumbnail

        bitmap   = extractThumbnail(bitmap,

                TARGET_SIZE_MICRO_THUMBNAIL,

                TARGET_SIZE_MICRO_THUMBNAIL,   OPTIONS_RECYCLE_INPUT);

    }

    return bitmap;

}

 

这里你可能注意到了,如果从EXIF的代码中获取本身文件缩略图用的是decodeFile(),而后面非JPG图片获取缩略图用decodeFileDescriptor(),为什么呢?
不知道,也许是开发者“Ray Chen”忘记了,只改了一部分,另外一部分为了稳定性也没改。
据网上资料看,decodeFileDescriptor()比decodeFile()更省内存,没有论证,仅供参考。

未使用的无用代码

在ThumbnailUtils有一些私有方法,但是自己又没有去调用,暂且把这些方法定位无用代码吧:

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

/**

 * Make a bitmap from a given Uri,   minimal side length, and maximum number of pixels.

 * The image data will be read from   specified pfd if it's not null, otherwise

 * a new input stream will be   created using specified ContentResolver.

 *

 * Clients are allowed to pass their   own BitmapFactory.Options used for bitmap decoding. A

 * new BitmapFactory.Options will be   created if options is null.

 */

private static Bitmap   makeBitmap(int minSideLength,   int maxNumOfPixels,

        Uri   uri, ContentResolver cr, ParcelFileDescriptor pfd,

        BitmapFactory.Options   options) {

    Bitmap b = null;

    try {

        if (pfd == null) pfd = makeInputStream(uri,   cr);

        if (pfd == null) return null;

        if (options == null) options = new BitmapFactory.Options();

        FileDescriptor   fd = pfd.getFileDescriptor();

        options.inSampleSize   = 1;

        options.inJustDecodeBounds   = true;

        BitmapFactory.decodeFileDescriptor(fd,   null, options);

        if (options.mCancel || options.outWidth == -1

                ||   options.outHeight == -1) {

            return null;

        }

        options.inSampleSize   = computeSampleSize(

                options,   minSideLength, maxNumOfPixels);

        options.inJustDecodeBounds   = false;

        options.inDither   = false;

        options.inPreferredConfig   = Bitmap.Config.ARGB_8888;

        b   = BitmapFactory.decodeFileDescriptor(fd, null, options);

    } catch (OutOfMemoryError ex) {

        Log.e(TAG,   "Got oom exception ", ex);

        return null;

    } finally {

        closeSilently(pfd);

    }

    return b;

}

private static void closeSilently(ParcelFileDescriptor c) {

    if (c == null) return;

    try {

        c.close();

    } catch (Throwable t) {

        //   do nothing

    }

}

private static ParcelFileDescriptor   makeInputStream(

        Uri   uri, ContentResolver cr) {

    try {

        return cr.openFileDescriptor(uri, "r");

    } catch (IOException ex) {

        return null;

    }

}

 

小结

通过学习ThumbnailUtils生成缩略图的方方面面,结合自己的经验实践,从此生成缩略图无忧。
零零散散写的有点乱,但基本上能运行到的每行代码都覆盖到了,对于理解ThumbnailUtils这个类来说,应该够了。

原文链接:http://www.apkbus.com/blog-705730-61815.html

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消