图片基础知识梳理(3) Bitmap&BitmapFactory 解析

1、概述

今天这篇文章咱们来了解一下两个类:canvas

  • Bitmap
  • BitmapFactory

2、Bitmap

2.1 建立Bitmap

经过Bitmap的源码,咱们能够看到它内部提供了不少.createBitmap(xxx)的静态方法,咱们能够经过这些方法来得到一个Bitmap数组

上述的方法最终能够分为如下三类:

  • 经过一个已有的Bitmap建立
  • 建立一个空的Bitmap
  • 建立一个新的Bitmap,该Bitmap每一个像素点的颜色经过一个colors[]数组指定。

下面,咱们来看一下这三类方法对于Bitmap的生产过程:bash

第一类

public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height,
            Matrix m, boolean filter) {

        checkXYSign(x, y);
        checkWidthHeight(width, height);
        //新的bitmap范围不能大于原始的bitmap
        if (x + width > source.getWidth()) {
            throw new IllegalArgumentException("x + width must be <= bitmap.width()");
        }
        if (y + height > source.getHeight()) {
            throw new IllegalArgumentException("y + height must be <= bitmap.height()");
        }

        //若是知足下面这些条件,那么直接返回原始的bitmap
        if (!source.isMutable() && x == 0 && y == 0 && width == source.getWidth() &&
                height == source.getHeight() && (m == null || m.isIdentity())) {
            return source;
        }

        int neww = width;
        int newh = height;
        Canvas canvas = new Canvas();
        Bitmap bitmap;
        Paint paint;
        //生成bitmap对应区域
        Rect srcR = new Rect(x, y, x + width, y + height);
        //原始bitmap对应区域
        RectF dstR = new RectF(0, 0, width, height);

        Config newConfig = Config.ARGB_8888;
        //得到原始bitmap的config
        final Config config = source.getConfig();
        // GIF files generate null configs, assume ARGB_8888
        if (config != null) {
            switch (config) {
                case RGB_565:
                    newConfig = Config.RGB_565;
                    break;
                case ALPHA_8:
                    newConfig = Config.ALPHA_8;
                    break;
                //noinspection deprecation
                case ARGB_4444:
                case ARGB_8888:
                default:
                    newConfig = Config.ARGB_8888;
                    break;
            }
        }
        //若是不须要变换,那么建立一个空的bitmap.
        if (m == null || m.isIdentity()) {
            bitmap = createBitmap(neww, newh, newConfig, source.hasAlpha());
            paint = null;   // not needed
        } else {
            //根据Matrix,对原始的bitmap进行一些变换操做.
            final boolean transformed = !m.rectStaysRect();

            RectF deviceR = new RectF();
            m.mapRect(deviceR, dstR);

            neww = Math.round(deviceR.width());
            newh = Math.round(deviceR.height());

            bitmap = createBitmap(neww, newh, transformed ? Config.ARGB_8888 : newConfig,
                    transformed || source.hasAlpha());

            canvas.translate(-deviceR.left, -deviceR.top);
            canvas.concat(m);

            paint = new Paint();
            paint.setFilterBitmap(filter);
            if (transformed) {
                paint.setAntiAlias(true);
            }
        }
        //返回bitmap的这些属性和原始bitmap相同
        bitmap.mDensity = source.mDensity;
        bitmap.setHasAlpha(source.hasAlpha());
        bitmap.setPremultiplied(source.mRequestPremultiplied);

        //设置canvas对应的bitmap为返回的bitmap
        canvas.setBitmap(bitmap);

        //经过canvas把原始的bitmap绘制上去.
        canvas.drawBitmap(source, srcR, dstR, paint);

        //从新置为空.
        canvas.setBitmap(null);
        return bitmap;
    }
复制代码
  • 方法做用:返回原始的Bitmap中一个不可改变的子集,返回的Bitmap有多是原始的Bitmap(原始的Bitmap不可改变,而且大小和请求的新的Bitmap大小和原来同样),也有多是复制出来的,它和原始的Bitmapdensity相同。
  • 参数说明:
  • source:原始的Bitmap
  • x, y:在原始的Bitmap中的起始坐标。
  • width, height:返回的Bitmap的宽高,若是超过了原始Bitmap的范围,那么会抛出异常。
  • mMatrix类型,表示须要的变换
  • filter:是否须要优化,只有当m不仅有平移操做时才去进行。

第二类

private static Bitmap createBitmap(DisplayMetrics display, int width, int height, Config config, boolean hasAlpha) {
        if (width <= 0 || height <= 0) {
            throw new IllegalArgumentException("width and height must be > 0");
        }
        Bitmap bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true);
        if (display != null) {
            bm.mDensity = display.densityDpi;
        }
        bm.setHasAlpha(hasAlpha);
        if (config == Config.ARGB_8888 && !hasAlpha) {
            nativeErase(bm.mNativePtr, 0xff000000);
        }
        return bm;
    }
复制代码
  • 方法做用:返回一个可变的bitmap,它的density由传入的DisplayMetrics指定。
  • 参数说明:
  • displayBitmap将要被绘制的Display metrics
  • width, heightbitmap的宽高
  • config:配置信息,对应ARGB_8888那些。
  • hasAlpha:若是bitmap的属性是ARGB_8888,那么这个标志为能够用来把bitmap标志为透明,它会把bitmap中的黑色像素转换为透明。

第三类

public static Bitmap createBitmap(DisplayMetrics display, int colors[],
            int offset, int stride, int width, int height, Config config) {

        checkWidthHeight(width, height);
        if (Math.abs(stride) < width) {
            throw new IllegalArgumentException("abs(stride) must be >= width");
        }
        int lastScanline = offset + (height - 1) * stride;
        int length = colors.length;
        if (offset < 0 || (offset + width > length) || lastScanline < 0 ||
                (lastScanline + width > length)) {
            throw new ArrayIndexOutOfBoundsException();
        }
        if (width <= 0 || height <= 0) {
            throw new IllegalArgumentException("width and height must be > 0");
        }
        Bitmap bm = nativeCreate(colors, offset, stride, width, height,
                            config.nativeInt, false);
        if (display != null) {
            bm.mDensity = display.densityDpi;
        }
        return bm;
    }
复制代码
  • 方法做用:返回一个不可变的bitmap对象,它的长宽由width/height指定,每一个像素点的颜色经过colos[]数组获得,初始的density来自于DisplayMetrics
  • 方法参数:
  • displayBitmap将要被绘制的Display metrics
  • colors:用来初始化像素点的颜色
  • offset:第一个像素点的颜色在数组当中跳过的个数。
  • stride:两行之间须要跳过的颜色个数。
  • width/height:宽高。
  • config:对应ARGB_8888那些。

2.2 压缩bitmap

public boolean compress(CompressFormat format, int quality, OutputStream stream) {
        checkRecycled("Can't compress a recycled bitmap");
        // do explicit check before calling the native method
        if (stream == null) {
            throw new NullPointerException();
        }
        if (quality < 0 || quality > 100) {
            throw new IllegalArgumentException("quality must be 0..100");
        }
        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "Bitmap.compress");
        boolean result = nativeCompress(mNativePtr, format.nativeInt,
                quality, stream, new byte[WORKING_COMPRESS_STORAGE]);
        Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
        return result;
    }
复制代码
  • 方法做用:把当前这个bitmap的压缩版本写入到某个输出流当中,若是返回true,那么这个bitmap能够被BitmapFactory.decodeStream()恢复。须要注意的是,并非全部的bitmap都支持全部的格式,所以,经过BitmapFactory恢复回来的bitmap有可能和原来不一样。
  • 方法参数:
  • CompressFormat是一个枚举类型,它的值有JPEG/PNG/WEBP
  • quality对应0-100
  • stream则是压缩后结果的输出流。

2.3 回收bitmap

public void recycle() {
        if (!mRecycled && mNativePtr != 0) {
            if (nativeRecycle(mNativePtr)) {
                // return value indicates whether native pixel object was actually recycled.
                // false indicates that it is still in use at the native level and these
                // objects should not be collected now. They will be collected later when the
                // Bitmap itself is collected.
                mBuffer = null;
                mNinePatchChunk = null;
            }
            mRecycled = true;
        }
    }
复制代码

recycle方法主要作几件事:ide

  • 释放和这个bitmap关联的native对象
  • 清除像素数据mBuffer的引用,可是这一过程不是同步的,它只是将引用置为空,等待垃圾回收器将它回收。
  • 在调用这个方法以后,mRecycled标志位就为true,以后若是再调用bitmap的方法,那么颇有可能发生异常。
  • 通常状况下,咱们不须要手动调用这个方法,由于当这个bitmap不被引用时,垃圾回收器就会自动回收它所占用的内存。

2.4 获取Bitmap所占内存

  • getAllocationByteCount() 返回存储这个bitmap对象所须要的内存,当咱们对bitmap所占内存区域进行复用的时候,这个函数的返回结果可能要大于getByteCount的值,不然,它和getByteCount的值是相同的。 这个值,在bitmap整个生命周期以内都不会改变。
public final int getAllocationByteCount() {
        if (mBuffer == null) {
            return getByteCount();
        }
        return mBuffer.length;
    }
复制代码
  • getByteCount 表示存储bitmap像素所须要的最小字节,自从4.4以后,这个就不能用来肯定bitmap占用的内存了,须要用getAllocationByteCount
public final int getByteCount() {
        // int result permits bitmaps up to 46,340 x 46,340
        return getRowBytes() * getHeight();
    }
复制代码

2.5 获取缩放后大小

咱们能够经过上面这六个方法得到缩放后的宽高,它们的原理就是传入一个目标的 density,而后和当前 bitmapdensity进行比较,而后算出一个缩放的倍数,在和原来的大小相乘。目标 density的来源有如下三个:

  • 直接传入
  • Canvasdensity
  • DisplayMetricsdensity

计算的规则为:函数

static public int scaleFromDensity(int size, int sdensity, int tdensity) {
        if (sdensity == DENSITY_NONE || tdensity == DENSITY_NONE || sdensity == tdensity) {
            return size;
        }

        // Scale by tdensity / sdensity, rounding up.
        return ((size * tdensity) + (sdensity >> 1)) / sdensity;
    }
复制代码

3、BitmapFactory

BitmapFactory用来从多种不一样的来源得到Bitmap优化

  • 文件、文件描述符
  • 资源文件Resource
  • byte[]数组
  • 输入流InputStream

3.1 BitmapFactory.Options

  • Bitmap inBitmap 若是给Options设置了这个Bitmap,那么在经过这个Options解码的时候,解码方法返回的bitmap会尝试复用这个Options中的bitmap,若是不能复用,那么解码方法会返回null,并抛出异常,它要求复用的bitmap是可变的。 在4.4之后,只要求新申请的bitmapgetByteCount()小于等于Options中的bitmapgetAllocationByteCount()就能够。 在4.4之前,格式必须是jpeg/png,而且要求两个bitmap相同而且inSampleSize1
  • boolean inJustDecodeBounds 若是设为true,那么解码方法的返回值null,可是它会设置outXXX的值,这样调用者就能够在不用解码整张图片的前提下查询到这个bitmap的长宽。
  • int inSampleSize 对原来的图片进行采样,若是inSampleSize4,那么图片的长宽会缩短为原来的1/4,这样就能够减小bitmap占用的内存。
  • Bitmap.Config inPreferredConfig 图片解码的格式要求。
  • 缩放相关的标志:inScaledinDensityinTargetDensityinScreenDensity 首先,只有在inScaledtrue的时候,缩放的机制才会生效,这个值默认是true的。
  • inDensity 咱们先讨论一下inDensity,当咱们没有给density赋值的时候,系统会给咱们初始化它:
//若是没有设置density
        if (opts.inDensity == 0 && value != null) {
            final int density = value.density; //这里的TypeValue会根据存放文件夹的不一样而不一样.
            if (density == TypedValue.DENSITY_DEFAULT) {
                opts.inDensity = DisplayMetrics.DENSITY_DEFAULT; //若是density为0,那么把density设置为160.
            } else if (density != TypedValue.DENSITY_NONE) {
                opts.inDensity = density; //不然,设置为value中的density.
            }
        }
复制代码
  • inTargetDensity 再来看一下inTargetDensity,它获得的就是屏幕的density.
if (opts.inTargetDensity == 0 && res != null) {
            opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
        }
复制代码
  • inScreenDensity 最后inScreenDensity没有被赋予默认值,也就是说它为0,若是咱们指望图片不要被缩放,那么就要给它设置为手机的density

这三者的关系是:inDensity不为0而且inTargetDensity不为0inDensityinScreenDensity不相等时,会对图片进行缩放,缩放倍数为inTargetDensity/inDensityui

这样说可能比较抽象,咱们举一个实际的例子,假如咱们的手机的density320dpi的,那么inTargetDensity就等于320,这时候咱们把某张图片资源放在了drawable-xxxhpi下,那么inDensity的值就为640,咱们没有设置inScreenDensity,那么它的默认值是0,这时候知足:spa

inDensity != 0 && inTargetDensity != 0 && inDensity != inScreenDensity
复制代码

图片就会进行缩放,缩放的倍数就为320/640,也就是说最终获得的bitmap的长宽是原来的一半。code

  • outXXX 这个返回的结果和inJustDecodeBounds有关,若是inJustDecodeBoundstrue,那么返回的是没有通过缩放的大小,若是为false,那么就是缩放后的大小。

3.2 获取bitmap方法

下面是BitmapFactory提供的方法: orm

全部的获取 bitmap最终都是调用了一下四个方法 Native方法其中之一,能够看到它能够从这些来源读取:

  • file
  • byte[]
  • InputStream

其中有个须要注意的是Rect,这是一个传入的值,在读取资源完毕后,它会写入读取资源的padding,若是没有那么为[-1, -1, -1,- 1],而若是返回的bitmap为空,那么传入的值不会改变。

4、Bitmap的转换方法

public class BitmapConvertUtils {

    public static Bitmap fromResourceIdAutoScale(Resources resources, int resourceId, BitmapFactory.Options options) {
        return BitmapFactory.decodeResource(resources, resourceId, options);
    }

    public static Bitmap fromResourceIdNotScale(Resources resources, int resourceId, Rect rect, BitmapFactory.Options options) {
        InputStream resourceStream = null;
        Bitmap bitmap = null;
        try {
            resourceStream = resources.openRawResource(resourceId);
            bitmap = BitmapFactory.decodeStream(resourceStream, rect, options);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (resourceStream != null) {
                    resourceStream.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return bitmap;
    }

    public static Bitmap fromAssert(Context context, String assertFilePath, Rect rect, BitmapFactory.Options options) {
        Bitmap bitmap = null;
        InputStream assertStream = null;
        AssetManager assetManager = context.getAssets();
        try {
            assertStream = assetManager.open(assertFilePath);
            bitmap = BitmapFactory.decodeStream(assertStream, rect, options);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (assertStream != null) {
                    assertStream.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
        return bitmap;
    }

    public static Bitmap fromByteArray(byte[] byteArray, int offset, int length, BitmapFactory.Options options) {
        return BitmapFactory.decodeByteArray(byteArray, offset, length, options);
    }

    public static Bitmap fromFile(String filePath, BitmapFactory.Options options) {
        return BitmapFactory.decodeFile(filePath, options);
    }

    public static Bitmap fromDrawable(Drawable drawable) {
        int width = drawable.getIntrinsicWidth();
        int height = drawable.getIntrinsicHeight();
        Bitmap bitmap = Bitmap.createBitmap(width, height, drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565);
        if (bitmap != null) {
            Canvas canvas = new Canvas(bitmap);
            drawable.setBounds(0, 0, width, height);
            drawable.draw(canvas);
            return bitmap;
        }
        return null;
    }

    public static Bitmap fromView(View view) {
        view.clearFocus();
        view.setPressed(false);
        boolean willNotCache = view.willNotCacheDrawing();
        view.setWillNotCacheDrawing(false);
        int color = view.getDrawingCacheBackgroundColor();
        view.setDrawingCacheBackgroundColor(color);
        if (color != 0) {
            view.destroyDrawingCache();
        }
        view.buildDrawingCache();
        Bitmap cacheBitmap = view.getDrawingCache();
        if (cacheBitmap == null) {
            return null;
        }
        Bitmap bitmap = Bitmap.createBitmap(cacheBitmap);
        view.destroyDrawingCache();
        view.setWillNotCacheDrawing(willNotCache);
        view.setDrawingCacheBackgroundColor(color);
        return bitmap;
    }

    public static Bitmap fromInputStream(InputStream inputStream) {
        return BitmapFactory.decodeStream(inputStream);
    }

    public static byte[] toByteArray(Bitmap bitmap, Bitmap.CompressFormat format, int quality) {
        byte[] bytes = null;
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        bitmap.compress(format, quality, outputStream);
        bytes = outputStream.toByteArray();
        try {
            outputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bytes;
    }

    public static Drawable toDrawable(Resources resources, Bitmap bitmap) {
        return new BitmapDrawable(resources, bitmap);
    }

    public static void toFile(Bitmap bitmap, Bitmap.CompressFormat format, int quality, String path) {
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(path);
            bitmap.compress(format, quality, fileOutputStream);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
}
复制代码
相关文章
相关标签/搜索