Bitmap简单使用及简单解析

一.Bitmap

内容如下:

1.Bitmap的生成

2.bitmap缩放、等图像变换

3.bitmap模糊处理

4.bitmap保存图像文件

5.Bitmap的防止内存泄露小方法

6.小知识点

 

1.Bitmap的生成

/**
     * 由本地文件路径、网络url或者项目的资源文件,生成Bitmap(旧,极端情况下可能造成OOM)
     * @param filePath
     */
    private void productBitmap(String filePath){
        Bitmap des_bitmap = null;
        BitmapFactory.Options options  = new BitmapFactory.Options();
//        options.inPreferredConfig
        //本地文件路径或者网络url
        Uri uri = Uri.parse(filePath);
        des_bitmap = BitmapFactory.decodeFile(uri.toString(),options);

        //项目资源文件
        des_bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher);

        //流,例如文件流
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(filePath);
            des_bitmap = BitmapFactory.decodeStream(fis,null,options);
            fis.close();
            fis = null;
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                if (fis != null)
                    fis.close();
            } catch (Exception e) {

            }
        }

        if(iv_bitmap_test!=null) {
            iv_bitmap_test.setImageBitmap(des_bitmap);
        }else{
            iv_bitmap_test = (ImageView) findViewById(R.id.iv_bitmap_test);
            iv_bitmap_test.setImageBitmap(des_bitmap);
        }
    }

 

/**
     * 获取截屏
     *
     * @param view 要截屏的view
     * @return 位图
     */
    private Bitmap getScreenShot(View view) {
        View v = view;
        v.setDrawingCacheEnabled(true);
        Bitmap bitmap = Bitmap.createBitmap(v.getDrawingCache());
        return bitmap;
    }

  

  

 

2.bitmap缩放、等图像变换

(1)长宽相同比例缩放

用BitmapFactory的decodeFile方法,然后通过传递进去 BitmapFactory.Option类型的参数进行取缩略图,在Option中,属性值inSampleSize表示缩略图大小为原始图片大小的几分之一,即如果这个值为2,则取出的缩略图的宽和高都是原始图片的1/2,图片大小就为原始大小的1/4。

经过阅读文档发现,Options中有个属性inJustDecodeBounds,文档中的是这么说的:

If set to true, the decoder will return null (no bitmap), but the out... fields will still be set, allowing the caller to query the bitmap without having to allocate the memory for its pixels.  

意思就是说如果该值设为true那么将不返回实际的bitmap对象,不给其分配内存空间但是可以得到一些解码边界信息即图片大小等信息。因此我们可以通过设置inJustDecodeBounds为true,获取到outHeight(图片原始高度)和 outWidth(图片的原始宽度),然后计算一个inSampleSize(缩放值),就可以取图片了,这里要注意的是,inSampleSize 可能等于0,必须做判断。也就是说先将Options的属性inJustDecodeBounds设为true,先获取图片的基本大小信息数据(信息没有保存在bitmap里面,而是保存在options里面),通过options.outHeight和 options. outWidth获取的大小信息以及自己想要到得图片大小计算出来缩放比例inSampleSize,然后紧接着将inJustDecodeBounds设为false,就可以根据已经得到的缩放比例得到自己想要的图片缩放图了。

/**
     * 由File转成长宽固定相同比例的bitmap。实现长宽等比例缩放
     * @return
     */
    private Bitmap resizeBitmap(String filePath){
        Bitmap des_bitmap ;

        BitmapFactory.Options options = new BitmapFactory.Options();
        //表示缩略图大小为原始图片大小的几分之一,即如果这个值为2,则取出的缩略图的宽和高都是原始图片的1/2,图片大小就为原始大小的1/4
        options.inJustDecodeBounds = true;
        des_bitmap = BitmapFactory.decodeFile(filePath,options);//des_bitmap为null.因为options.inJustDecodeBounds = true,节省内存

        //长宽按照相同比例缩放
        int height = options.outHeight;
        int width = options.outWidth;
        int scale;//缩放值,因为为等比例缩放.
        if(height > width){//以高为准
            scale = (int) (height/200.0f);//该200说明目标大小为200PX
        }else{
            scale = (int) (width/200.0f);//该200说明目标大小为200PX
        }
        if(scale<=0){
            scale = 1;
        }
        options.inSampleSize = scale;
        options.inJustDecodeBounds = false;
        Bitmap new_bitmap = BitmapFactory.decodeFile(filePath,options);//des_bitmap不为null
        if(des_bitmap!=new_bitmap){
            des_bitmap.recycle();//注意销毁不用的bitmap
        }
        return new_bitmap;
    }

 

(2)Bitmap与Matrix搭配,实现缩放或者其他形变例如旋转

如果对Matrix矩阵类没有多少概念或者概念不全,可进入Matrix详情了解

/**
     * 由Bitmap转成指定大小的Bitmap,实现缩放成指定准确大小Bitmap
     * @param filePath
     * @param sWidth 目标宽,单位为px
     * @param sHeight 目标高,单位为px
     */
    private Bitmap resizeBitmap(String filePath,int sWidth,int sHeight){
        //缩放到指定的长宽
        Bitmap mBitmap;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = false;
        mBitmap = BitmapFactory.decodeFile(filePath,options);
        if(mBitmap == null){
            return null;
        }
        int bmpWidth = mBitmap.getWidth();
        int bmpHeight = mBitmap.getHeight();
        // 缩放图片的尺寸
        float scaleWidth = (float) sWidth / bmpWidth; // 按固定大小缩放 sWidth 写多大就多大
        float scaleHeight = (float) sHeight / bmpHeight; //
        Matrix matrix = new Matrix();
    //matrix.postScale(1, -1); //镜像垂直翻转
    //matrix.postScale(-1, 1); //镜像水平翻转
    //matrix.postRotate(-90); //旋转-90度
matrix.postScale(scaleWidth, scaleHeight);// 产生缩放后的Bitmap对象 
Bitmap resizeBitmap = Bitmap.createBitmap(mBitmap, 0, 0, bmpWidth, bmpHeight, matrix, false);
mBitmap.recycle();
return resizeBitmap; }

 在控件画布上的使用:

重要的方法有

Bitmap.createBitmap(Bitmap source, int x, int y, int width, int height, Matrix m, boolean filter){}
canvas.drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) {}

 

Matrix mIconMatrix= new Matrix();
mIconMatrix.postScale(0.71f,0.71f);
final Bitmap start = BitmapFactory.decodeResource(getResources(), R.drawable.run_start);
bitmapStart = Bitmap.createBitmap(start,0,0,start.getWidth(),start.getHeight(),mIconMatrix,false);
protected void onDraw(Canvas canvas){
   mIconMatrix.postTranslate(123,123);//matrix的一系列变换
       canvas.drawBitmap(bitmapStart, mIconMatrix, null);
}

 

 

3.bitmap模糊处理

(一)自定义的模糊

 见下篇文章

(二)高斯模糊

下篇文章介绍

4.bitmap保存图像文件

(一)保存

/**
     * 保存内存中的bitmap到本地文件
     * @param bitmap 要保存的bitmap
     * @param localPath 保存在本地的文件名,绝对路径
     * */
    public static void saveBitmap2File(Bitmap bitmap,String localPath){
        if (bitmap == null) {
            return;
        }
        try {
            File file = new File(localPath);
            FileOutputStream fos = new FileOutputStream(file);
            assert bitmap != null;
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);//100表示正常质量,如果压缩之类的更改压缩质量即可
            fos.flush();
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

 (二)压缩

/**
     * 圆片压缩
     * 
     * @param bitmap
     * @return
     */
    private Bitmap compressImage(Bitmap image) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        image.compress(Bitmap.CompressFormat.JPEG, 100, baos);//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
      Log.e("size", baos.toByteArray().length/1024+"");
        ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());//把压缩后的数据baos存放到ByteArrayInputStream中
        Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);//把ByteArrayInputStream数据生成图片
        return bitmap;
    }

(三)Bitmap<->bytes转换

1.  public static Bitmap Bytes2Bimap(byte[] b) {  
2.         if (b.length != 0) {  
3.             return BitmapFactory.decodeByteArray(b, 0, b.length);  
4.         } else {  
5.             return null;  
6.         }  
7.     }  
1.  public static byte[] Bitmap2Bytes(Bitmap bm) {  
2.         ByteArrayOutputStream baos = new ByteArrayOutputStream();  
3.         bm.compress(Bitmap.CompressFormat.PNG, 100, baos);  
4.         return baos.toByteArray();  
5.     }  
6. 
 
1.  public static Bitmap drawableToBitmap(Drawable drawable) {  
2.   
3.         Bitmap bitmap = Bitmap  
4.                 .createBitmap(  
5.                         drawable.getIntrinsicWidth(),  
6.                         drawable.getIntrinsicHeight(),  
7.                         drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888  
8.                                 : Bitmap.Config.RGB_565);  
9.         Canvas canvas = new Canvas(bitmap);  
10.         // canvas.setBitmap(bitmap);  
11.         drawable.setBounds(0, 0, drawable.getIntrinsicWidth(),  
12.                 drawable.getIntrinsicHeight());  
13.         drawable.draw(canvas);  
14.         return bitmap;  
15.     } 

 

5.Bitmap的防止内存泄露小方法

(一)Bitmap占用了多少内存

①获取bitmap的内存大小

相关:

/**
     * Returns the minimum number of bytes that can be used to store this bitmap's pixels.
     *
     * <p>As of {@link android.os.Build.VERSION_CODES#KITKAT}, the result of this method can
     * no longer be used to determine memory usage of a bitmap. See {@link
     * #getAllocationByteCount()}.</p>
     */
    public final int getByteCount() {
        // int result permits bitmaps up to 46,340 x 46,340
        return getRowBytes() * getHeight();
    }

getByteCount()方法能获取该bitmap所占的最小内存,不过API19即4.4以后通过getAllocationByteCount()获取内存大小。

两者可能存在不一样的原因是因为如果bitmap重复利用的话,先存储形状大的图片再存储形状小些的图片,对于小的图片,通过getAllocationCount()获取到的大小会比getByteCount()要大。

/**
     * Returns the size of the allocated memory used to store this bitmap's pixels.
     *
     * <p>This can be larger than the result of {@link #getByteCount()} if a bitmap is reused to
     * decode other bitmaps of smaller size, or by manual reconfiguration. See {@link
     * #reconfigure(int, int, Config)}, {@link #setWidth(int)}, {@link #setHeight(int)}, {@link
     * #setConfig(Bitmap.Config)}, and {@link BitmapFactory.Options#inBitmap
     * BitmapFactory.Options.inBitmap}. If a bitmap is not modified in this way, this value will be
     * the same as that returned by {@link #getByteCount()}.</p>
     *
     * <p>This value will not change over the lifetime of a Bitmap.</p>
     *
     * @see #reconfigure(int, int, Config)
     */
    public final int getAllocationByteCount() {
        if (mBuffer == null) {
            // native backed bitmaps don't support reconfiguration,
            // so alloc size is always content size
            return getByteCount();
        }
        return mBuffer.length;
    }
②深入

在getByteCount()中,getHeight()标示bitmap的高度,单位为px。而getRowBytes()呢?往下

/**
     * Return the number of bytes between rows in the bitmap's pixels. Note that
     * this refers to the pixels as stored natively by the bitmap. If you call
     * getPixels() or setPixels(), then the pixels are uniformly treated as
     * 32bit values, packed according to the Color class.
     *
     * <p>As of {@link android.os.Build.VERSION_CODES#KITKAT}, this method
     * should not be used to calculate the memory usage of the bitmap. Instead,
     * see {@link #getAllocationByteCount()}.
     *
     * @return number of bytes between rows of the native bitmap pixels.
     */
    public final int getRowBytes() {
        if (mRecycled) {
            Log.w(TAG, "Called getRowBytes() on a recycle()'d bitmap! This is undefined behavior!");
        }
        return nativeRowBytes(mNativePtr);
    }

进入Android5.0源码进行JNI分析

\frameworks\base\core\jni\Android\graphics\Bitmap.cpp:

static jint Bitmap_rowBytes(JNIEnv* env, jobject, jlong bitmapHandle) {
     SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle)
     return static_cast<jint>(bitmap->rowBytes());
}

我们发现rowBytes()其实就是对SKBitmap的指针引用。因为bitmap指向的是skBitmap的所在地址。所以bitmap的大小即为SkBitmap的值。

SkBitmap.h:

/** Return the number of bytes between subsequent rows of the bitmap. */
size_t rowBytes() const { return fRowBytes; }

SkBitmap.cpp:

size_t SkBitmap::ComputeRowBytes(Config c, int width) {
    return SkColorTypeMinRowBytes(SkBitmapConfigToColorType(c), width);
}

static inline size_t SkColorTypeMinRowBytes(SkColorType ct, int width) {
    return width * SkColorTypeBytesPerPixel(ct);
}

SkImageInfo.h
 
static int SkColorTypeBytesPerPixel(SkColorType ct) {
   static const uint8_t gSize[] = {
    0,  // Unknown
    1,  // Alpha_8
    2,  // RGB_565
    2,  // ARGB_4444
    4,  // RGBA_8888
    4,  // BGRA_8888
    1,  // kIndex_8
  };
  SK_COMPILE_ASSERT(SK_ARRAY_COUNT(gSize) == (size_t)(kLastEnum_SkColorType + 1),
                size_mismatch_with_SkColorType_enum);
 
   SkASSERT((size_t)ct < SK_ARRAY_COUNT(gSize));
   return gSize[ct];
}
 

我发现大小即是width和一个ColorTypeBytesPerPixel的乘积。ColorTypeBytesPerPixel为bitmap的图片分辨率类型,在BitmapFactory.Option的inPreferredConfig属性决定,即不同图片

分辨率类型决定了不同的内存大小(例如ARGB_8888类型的分辨率图片每单位width下占4字节,该类型也是Bitmap默认的属性)。即rowBytes等于‘4*width’ Bytes。

但是通过计算,发现通过计算内存=4*width*height,仍然不对。

 

因此我猜想图片的内存大小除了图片自身的分辨率以外,还跟设备的显示密度、以及资源来源有关。

 

③再深入

我们以BitmapFactory.decodeResource()加载项目资源文件为例

/**
     * Synonym for opening the given resource and calling
     * {@link #decodeResourceStream}.
     *
     * @param res   The resources object containing the image data
     * @param id The resource id of the image data
     * @param opts null-ok; Options that control downsampling and whether the
     *             image should be completely decoded, or just is size returned.
     * @return The decoded bitmap, or null if the image data could not be
     *         decoded, or, if opts is non-null, if opts requested only the
     *         size be returned (in opts.outWidth and opts.outHeight)
     */
    public static Bitmap decodeResource(Resources res, int id, Options opts) {
        Bitmap bm = null;
        InputStream is = null; 
        
        try {
            final TypedValue value = new TypedValue();
            is = res.openRawResource(id, value);

            bm = decodeResourceStream(res, value, is, null, opts);
        } catch (Exception e) {
            /*  do nothing.
                If the exception happened on open, bm will be null.
                If it happened on close, bm is still valid.
            */
        } finally {
            try {
                if (is != null) is.close();
            } catch (IOException e) {
                // Ignore
            }
        }

        if (bm == null && opts != null && opts.inBitmap != null) {
            throw new IllegalArgumentException("Problem decoding into existing bitmap");
        }

        return bm;
    }
View Code

本质就两步:

1.调用Resource.openRawResource 方法,这个方法调用完成之后会对 TypedValue 进行赋值,其中包含了原始资源的 density 等信息

2.调用 decodeResourceStream 对原始资源进行解码和适配。这个过程实际上就是原始资源的 density 到屏幕 density 的一个映射。

decodeResourceStream():

/**
     * Decode a new Bitmap from an InputStream. This InputStream was obtained from
     * resources, which we pass to be able to scale the bitmap accordingly.
     */
    public static Bitmap decodeResourceStream(Resources res, TypedValue value,
            InputStream is, Rect pad, Options opts) {

        if (opts == null) {
            opts = new Options();
        }

        if (opts.inDensity == 0 && value != null) {
            final int density = value.density;
            if (density == TypedValue.DENSITY_DEFAULT) {
                opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
            } else if (density != TypedValue.DENSITY_NONE) {
                opts.inDensity = density;
            }
        }
        
        if (opts.inTargetDensity == 0 && res != null) {
            opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
        }
        
        return decodeStream(is, pad, opts);
    }
View Code

又回到了decodeStream(),我试过直接加载文件图片时调用该方法,参数为文件输入流。decodeStream(fis,null,option)。即参数Rect直接设为null。

与本例子不同的,即通过decodeResourceStream(),设置了BitmapFactory.Option的inDensity属性和inTargetDensity属性。

inDensity表示资源文件密度,如果对应资源目录为hdpi的话,就是240.

inTargetDensity标示显示的密度,如果是三星S6为640.

重要的是nativeDecodeStream方法,重要的的是其中的doDecode方法。

相关Bitmap的JNI解析:

BitmapFactory.cpp

static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {
 
......
    if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
        const int density = env->GetIntField(options, gOptions_densityFieldID);//对应hdpi的时候,是240
        const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);//三星s6的为640
        const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
        if (density != 0 && targetDensity != 0 && density != screenDensity) {
            scale = (float) targetDensity / density;
        }
    }
}
 
const bool willScale = scale != 1.0f;
......
SkBitmap decodingBitmap;
if (!decoder->decode(stream, &decodingBitmap, prefColorType,decodeMode)) {
   return nullObjectReturn("decoder->decode returned false");
}
//这里这个deodingBitmap就是解码出来的bitmap,大小是图片原始的大小
int scaledWidth = decodingBitmap.width();
int scaledHeight = decodingBitmap.height();
if (willScale && decodeMode != SkImageDecoder::kDecodeBounds_Mode) {
    scaledWidth = int(scaledWidth * scale + 0.5f);
    scaledHeight = int(scaledHeight * scale + 0.5f);
}
if (willScale) {
    const float sx = scaledWidth / float(decodingBitmap.width());
    const float sy = scaledHeight / float(decodingBitmap.height());
 
    // TODO: avoid copying when scaled size equals decodingBitmap size
    SkColorType colorType = colorTypeForScaledOutput(decodingBitmap.colorType());
    // FIXME: If the alphaType is kUnpremul and the image has alpha, the
    // colors may not be correct, since Skia does not yet support drawing
    // to/from unpremultiplied bitmaps.
    outputBitmap->setInfo(SkImageInfo::Make(scaledWidth, scaledHeight,
            colorType, decodingBitmap.alphaType()));
    if (!outputBitmap->allocPixels(outputAllocator, NULL)) {
        return nullObjectReturn("allocation failed for scaled bitmap");
    }
 
    // If outputBitmap's pixels are newly allocated by Java, there is no need
    // to erase to 0, since the pixels were initialized to 0.
    if (outputAllocator != &javaAllocator) {
        outputBitmap->eraseColor(0);
    }
 
    SkPaint paint;
    paint.setFilterLevel(SkPaint::kLow_FilterLevel);
 
    SkCanvas canvas(*outputBitmap);
    canvas.scale(sx, sy);
    canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
}
......
}
 

 

outputBitmap->setInfo(SkImageInfo::Make(scaledWidth, scaledHeight,
            colorType, decodingBitmap.alphaType()));

 

 

我们看到最终输出的 outputBitmap 的大小是scaledWidth*scaledHeight,我们把这两个变量计算的片段拿出来给大家一看就明白了:

if (willScale && decodeMode != SkImageDecoder::kDecodeBounds_Mode) {
    scaledWidth = int(scaledWidth * scale + 0.5f);
    scaledHeight = int(scaledHeight * scale + 0.5f);
}

在522*686的PNG 图片,我把它放到 drawable-xxhdpi 目录下,在三星s6上加载,占用内存2547360B。

scaledWidth = int( 522 * 640 / 480f + 0.5) = int(696.5) = 696

scaledHeight = int( 686 * 640 / 480f + 0.5) = int(915.16666…) = 915

下面就是见证奇迹的时刻:

915 * 696 * 4 = 2547360

④总结

Bitmap的内存使用与图片的分辨率属性有关以外,还跟所在的文件目录,投影的设备密度有关。

 

6.附加小知识

(一)Bitmap加水印

/**
     * 添加文字到图片,类似水印文字
     *
     * @param gContext
     * @param gResId
     * @param gText
     * @return
     */
    public static Bitmap drawTextToBitmap(Context gContext, int gResId, String gText) {
        Resources resources = gContext.getResources();
        float scale = resources.getDisplayMetrics().density;
        Bitmap bitmap = BitmapFactory.decodeResource(resources, gResId);
        android.graphics.Bitmap.Config bitmapConfig = bitmap.getConfig();    // set default bitmap config if none
        if (bitmapConfig == null) {
            bitmapConfig = android.graphics.Bitmap.Config.ARGB_8888;
        }    // resource bitmaps are imutable,    // so we need to convert it to mutable one
        bitmap = bitmap.copy(bitmapConfig, true);
        Canvas canvas = new Canvas(bitmap);    // new antialised Paint
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);    // text color - #3D3D3D
        paint.setColor(Color.rgb(61, 61, 61));    // text size in pixels
        paint.setTextSize((int) (14 * scale * 5));    // text shadow
        paint.setShadowLayer(1f, 0f, 1f, Color.WHITE);    // draw text to the Canvas center
        Rect bounds = new Rect();
        paint.getTextBounds(gText, 0, gText.length(), bounds);
        //int x = (bitmap.getWidth() - bounds.width()) / 2;
        //int y = (bitmap.getHeight() + bounds.height()) / 2;    //draw  text  to the bottom
        int x = (bitmap.getWidth() - bounds.width()) / 10 * 9;
        int y = (bitmap.getHeight() + bounds.height()) / 10 * 9;
        canvas.drawText(gText, x, y, paint);
        return bitmap;
    }

 

 

 

posted on 2017-04-17 19:43  右耳Deng  阅读(16546)  评论(0编辑  收藏  举报

导航