精华内容
下载资源
问答
  • Android中如何加载大图片和长图片

    千次阅读 2018-07-02 09:13:42
    但是如果我们要加载的图片远远大于ImageView的大小,直接用ImageView去展示的话,就会带来不好的视觉效果,也会占用太的内存和性能开销。甚至这张图片足够到导致程序oom崩溃。这个时候我们就需要对图片进行特殊...

    我们在做开发的时候总是会不可避免的遇到加载图片的情况,当图片的尺寸小于ImageView的尺寸的时候,我们当然可以很happy的去直接加载展示。但是如果我们要加载的图片远远大于ImageView的大小,直接用ImageView去展示的话,就会带来不好的视觉效果,也会占用太多的内存和性能开销。甚至这张图片足够大到导致程序oom崩溃。这个时候我们就需要对图片进行特殊的处理了:

    一、图片压缩

    图片太大,那我就想办法把它压缩变小呗。老铁,这思路完全没毛病。BitmapFactory这个类就提供了多个解析方法(decodeResourcedecodeStreamdecodeFile等)用于创建Bitmap。我们可以根据图片的来源来选择解析方法。比如如果图片来源于网络,就可以使用decodeStream方法;如果是sd卡里面的图片,就可以选择decodeFile方法;如果是资源文件里面的图片,就可以使用decodeResource方法等。这些方法会为创建的Bitmap分配内存,如果图片过大的话就会导致 oom。

    BitmapFactory为这些方法都提供了一个可选的参数BitmapFactory.Options,用来辅助我们解析图片。这个参数有一个属性inSampleSize,这个属性可以帮助我们来进行图片的压缩。为了解释inSampleSize的效果,我们可以举个栗子。比如我们有一张2048*1536的图片,设置inSampleSize的值为4,就可以把这张图片压缩为512*384,长短各缩小了4倍,所占内存就缩小了16倍。这就明了了,inSampleSize的作用就是可以把图片的长短缩小inSampleSize倍,所占内存缩小inSampleSize的平方。官方文档对于inSampleSize的值也做了一些要求,那就是inSampleSize的值必须大于等于1,如果给定的值小于1,那就默认为1。而且inSampleSize的值需要是2的倍数,如果不是的话,就会自动变为离这个值向下最近的2的倍数的值,比如给定的值是3,那么最终 inSampleSize的值会是2。

    当然了,这个inSampleSize的值我们也不可能随便就给,最好使我们能获取到照片的原始大小,再根据需要进行压缩。别急,谷歌都帮我们想好了!BitmapFactory.Options有一个属性inJustDecodeBounds,这个属性当为true的时候,表明我们当前只是为了获取当前图片的边界的大小,此时BitmapFactory的解析图片方法的返回值为 null,该方法是一个十分轻量级的方法。这样我们就可以很愉快的拿到图片大小了,代码如下:

    
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true; // 当前只为获取图片的边界大小
    BitmapFactory.decodeResource(getResources(), R.drawable.bigpic, options);
    int outHeight = options.outHeight;
    int outWidth = options.outWidth;
    String outMimeType = options.outMimeType;
    

    拿到了图片的大小,我们就可以根据需要计算出所需要压缩的大小了:

    
    private int caculateSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
            int sampleSize = 1;
            int picWidth = options.outWidth;
            int picHeight = options.outHeight;
            if (picWidth > reqWidth || picHeight > reqHeight) {
                int halfPicWidth = picWidth / 2;
                int halfPicHeight = picHeight / 2;
                while (halfPicWidth / sampleSize > reqWidth || halfPicHeight / sampleSize > reqHeight) {
                    sampleSize *= 2;
                }
            }
            return sampleSize;
    }
    

    下面就是完整的代码:

    
            mIvBigPic = findViewById(R.id.iv_big_pic);
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true; // 当前只为获取图片的边界大小
            BitmapFactory.decodeResource(getResources(), R.drawable.bigpic, options);
            int outHeight = options.outHeight;
            int outWidth = options.outWidth;
            String outMimeType = options.outMimeType;
            System.out.println("outHeight = " + outHeight + " outWidth = " + outWidth + " outMimeType = " + outMimeType);
            options.inJustDecodeBounds = false;
            options.inSampleSize = caculateSampleSize(options, getScreenWidth(), getScreenHeight());
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bigpic, options);
            mIvBigPic.setImageBitmap(bitmap);
    

    这样图片压缩到这里就差不多结束了。

    二、局部展示

    有时候我们通过压缩可以取得很好的效果,但有时候效果就不那么美好了,例如长图像清明上河图,像这类的长图,如果我们直接压缩展示的话,这张图完全看不清,很影响体验。这时我们就可以采用局部展示,然后滑动查看的方式去展示图片。

    Android里面是利用BitmapRegionDecoder来局部展示图片的,展示的是一块矩形区域。为了完成这个功能那么就需要一个方法设置图片,另一个方法设置展示的区域。

    • 初始化

    BitmapRegionDecoder提供了一系列的newInstance来进行初始化,支持传入文件路径,文件描述符和文件流InputStream

    例如:

    mRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
    

    上面这个方法解决了传入图片,接下来就要去设置展示区域了。

    Bitmap bitmap = mRegionDecoder.decodeRegion(mRect, sOptions);
    

    参数一是一个Rect,参数二是BitmapFactory.Options,可以用来控制inSampleSizeinPreferredConfig等。下面是一个简单的例子,展示图片最前面屏幕大的部分:

    
        try {
                BitmapRegionDecoder regionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
                BitmapFactory.Options options1 = new BitmapFactory.Options();
                options1.inPreferredConfig = Bitmap.Config.ARGB_8888;
                Bitmap bitmap = regionDecoder.decodeRegion(new Rect(0, 0, getScreenWidth(), getScreenHeight()), options1);
                mIvBigPic.setImageBitmap(bitmap);
            } catch (IOException e) {
                e.printStackTrace();
            }
    

    当然了,这只是最简单的用法,对于我们想要完全展示图片并没什么用!客官,稍安勿躁,前途已经明了!既然我们可以实现区域展示,那我们可不可以自定义一个View,可以随着我们的手指滑动展示图片的不同区域。yes! of course。那么我们就继续吧!

    根据上面的分析,我们自定义控件的思路就很明白了:

    • 提供一个设置图片的路口;
    • 重写onTouchEvent,根据用户移动的手势,修改图片显示的区域;
    • 每次更新区域参数后,调用invalidate,onDraw里面去regionDecoder.decodeRegion拿到bitmap,去draw

    废话不多说,直接上代码:

    
    public class BigImageView extends View {
        private static final String TAG = "BigImageView";
    
        private BitmapRegionDecoder mRegionDecoder;
        private int mImageWidth, mImageHeight;
        private Rect mRect = new Rect();
        private static BitmapFactory.Options sOptions = new BitmapFactory.Options();
        {
            sOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
        }
    
        public BigImageView(Context context) {
            this(context, null);
        }
    
        public BigImageView(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public BigImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
        public void setInputStream(InputStream inputStream) {
            try {
                mRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inJustDecodeBounds = false;
                BitmapFactory.decodeStream(inputStream, null, options);
                mImageHeight = options.outHeight;
                mImageWidth = options.outWidth;
    
                requestLayout();
                invalidate();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        int downX = 0;
        int downY = 0;
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    downX = (int) event.getX();
                    downY = (int) event.getY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    int curX = (int) event.getX();
                    int curY = (int) event.getY();
    
                    int moveX = curX - downX;
                    int moveY = curY - downY;
    
                    onMove(moveX, moveY);
    
                    System.out.println(TAG + " moveX = " + moveX + " curX = " + curX + " downX = " + downX);
    
                    downX = curX;
                    downY = curY;
                    break;
                case MotionEvent.ACTION_UP:
                    break;
            }
            return true;
        }
    
        private void onMove(int moveX, int moveY) {
            if (mImageWidth > getWidth()) {
                mRect.offset(-moveX, 0);
                checkWidth();
                invalidate();
            }
    
            if (mImageHeight > getHeight()) {
                mRect.offset(0, -moveY);
                checkHeight();
                invalidate();
            }
    
        }
    
        private void checkWidth() {
            Rect rect = mRect;
            if (rect.right > mImageWidth) {
                rect.right = mImageWidth;
                rect.left = mImageWidth - getWidth();
            }
    
            if (rect.left < 0) {
                rect.left = 0;
                rect.right = getWidth();
            }
        }
    
        private void checkHeight() {
            Rect rect = mRect;
            if (rect.bottom > mImageHeight) {
                rect.bottom = mImageHeight;
                rect.top = mImageHeight - getHeight();
            }
    
            if (rect.top < 0) {
                rect.top = 0;
                rect.bottom = getWidth();
            }
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
            int width = getMeasuredWidth();
            int height = getMeasuredHeight();
    
            mRect.left = 0;
            mRect.top = 0;
            mRect.right = width;
            mRect.bottom = height;
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            Bitmap bitmap = mRegionDecoder.decodeRegion(mRect, sOptions);
            canvas.drawBitmap(bitmap, 0, 0, null);
        }
    }
    

    根据上述源码:

    • setInputStream方法里面初始BitmapRegionDecoder,获取图片的实际宽高;
    • onMeasure方法里面给Rect赋初始化值,控制开始显示的图片区域;
    • onTouchEvent监听用户手势,修改Rect参数来修改图片展示区域,并且进行边界检测,最后invalidate;
    • onDraw里面根据Rect获取Bitmap并且绘制。

     

     


    转载链接:https://www.jianshu.com/p/4640764bfbc6
     

     

     

    展开全文
  • android 图片占用内存大小及加载解析

    万次阅读 多人点赞 2017-04-01 16:59:24
    在讲解图片占用内存前,我们先问自己几个问题: 我们在对手机进行屏幕适时,常想可不可以只切一套图适配所有的手机呢? 一张图片加载到手机中,占用内存到底有多少? 图片占用内存跟哪些东西有关?跟手机有关系么?...

    *本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布
    在讲解图片占用内存前,我们先问自己几个问题:

    • 我们在对手机进行屏幕适时,常想可不可以只切一套图适配所有的手机呢?
    • 一张图片加载到手机中,占用内存到底有多少?
    • 图片占用内存跟哪些东西有关?跟手机有关系么?同一张图片放在不同的dpi文件夹下内存占用会变化么?
    • 如果是网络图片,加载到手机中,占用内存跟手机屏幕有关系么?

      带着这些问题我们来一层层解析。我们先看看加载本地资源,不同手机所占内存情况:

    一、加载本地资源,不同手机占内存情况

    我们如果加载app内图片,想知道它占用多少内存,可先将此资源转成bitmap进行查看。

    1. 从资源中获取bitmap

    Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.mipmap.testxh);

    获取到bitmap,我们还需要知道此bitmap在内存占多少空间,具体方法如下。

    2. 获取图片大小

    public int getBitmapSize(Bitmap bitmap){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){     //API 19
            return bitmap.getAllocationByteCount();
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1){//API 12
            return bitmap.getByteCount();
        } else {
            return bitmap.getRowBytes() * bitmap.getHeight(); //earlier version
        }
    }

    接下来就来测试,不同的手机、同一张图片放在不同的密度文件夹下,占用内存情况。

    3. 同一图片在不同屏幕的手机、不同的屏幕密度文件夹下占用内存大小

    (1) 经测试同一张图片分别放在不同的mipmap文件夹(mipmap-hdpi, mipmap-xhdpi, mipmap-xxhdpi)下或是drawable文件夹(drawable-hdpi, drawable-xhdpi, drawable-xxhdpi)下,相同的dpi下的文件夹下加载出来的图片,bitmap占用内存大小一样;

    (2) 对于同一张图片,放在不同手机、不同的屏幕密度文件夹下占用内存情况又是如何呢,这里我们以一张大小为1024*731 = 748544B, 大小为485.11K 的图片为例,下面是测试手机占用的内存情况。
    对于不同的手机屏幕密度的手机占用内存大小
    从上表可以看出不同屏幕密度的手机加载图片,如果图片放在与自己屏幕密度相同的文件夹下,占用的内存都是2994176B,与图片本身大小748544B存在一个4倍关系,因为图片采用的ARGB-888色彩格式,每个像素点占用4个字节。

    从上述测试可以得出,bitmap占用内存大小,与手机的屏幕密度、图片所放文件夹密度、图片的色彩格式有关。
    这里总结一下获取Bitmap图片大小的代码:
    手机在加载图片时,会先查找自己本密度的文夹下是否存在资源,不存在则会向上查找,再向下查找,并对图片进行相应倍数的缩放:

    1. 如果在与自己屏幕密度相同的文件夹下存在此资源,会原样显示出来,占用内存正好是: 图片的分辨率*色彩格式占用字节数;

    2. 若自己屏幕密度相同的文件夹下不存在此文件,而在大于自己屏幕密度的文件夹下存在此资源,会进行缩小相应的倍数的平方;

    3. 若在大于自己屏幕密度的文件夹下没找到此资源,则会向小于自己屏幕密度的文件夹下查找,如果存在,则会进行放大相应的倍数的平方,这两种情况图片占用内存为:
      占用内存=图片宽度 X 图片高度/((资源文件夹密度/手机屏幕密度)^2) * 色彩格式每一个像素占用字节数

    4. 图片占用内存与图片的色彩格式的关系

    我们在计算bitmap大小时,是通过计算getRowBytes * bitmap.getHeight()得来的,后面的乘数就是图片的高度,而第一个乘数getRowBytes是什么呢?我们根进Bitmap代码查看getRowBytes函数:

    /**
         * 返回位图像素的行的字节数,由位图存储的像素值有关,它会根据Color类进行打包
         */
        public final int getRowBytes() {
            if (mRecycled) {
                Log.w(TAG, "Called getRowBytes() on a recycle()'d bitmap! This is undefined behavior!");
            }
            return nativeRowBytes(mNativePtr);
        }

    该方法最终调用的是Bitmap中的native方法:

    private static native int nativeRowBytes(long nativeBitmap);

    我们再查看对应的Bitmap.cpp里的nativeRowBytes方法

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

    我们可以看到这里的bitmap形式是以SkBitmap对象展现的,这个Bitmap就和图片展示的色彩格式有关,我们再看看SkBitmap里是怎么计算rowBytes的:

    size_t SkBitmap::ComputeRowBytes(Config c, int width) {
        return SkColorTypeMinRowBytes(SkBitmapConfigToColorType(c), width);
    }
    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];
    }
    
    static inline size_t SkColorTypeMinRowBytes(SkColorType ct, int width) {
        return width * SkColorTypeBytesPerPixel(ct);
    }

    可以看到,图片的宽乘以了一个SkColorTypeBytesPerPixel(ct)变量,对于不同色彩格式,每个像素占用的字节数就是在SkColorTypeBytesPerPixel中定义的。这就是为什么上面得出的bitmap大小,在自己屏幕密度的文件夹下图片占用的内存大小都被乘以了4,因为bitmap加载默认采用的是RGBA_8888编码格式。

    5. 图片占用内存与手机屏幕密度、图片所在文件夹密度的关系

    那么手机怎么加载图片时,为什么同样的图片在不同的屏幕分辨率的手机上、不同的屏幕密度文件夹下占用内存会相差这么大呢?

    在加载资源图片时,我们一般会借助于BitmapFactory的decodeResource方法,此方法的源代码如下:

     /**
         * @param res   包含图片资源的Resources对象,一般通过getResources()即可获取
         * @param id    资源文件id, 如R.mipmap.ic_laucher
         * @param opts  可为空,控制采样或图片是否需要完全解码还是只需要获取图片大小
         * @return      解码的bitmap
         */
        public static Bitmap decodeResource(Resources res, int id, Options opts) {
            Bitmap bm = null;
            InputStream is = null; 
    
            try {
                final TypedValue value = new TypedValue();
                //1.读取资源返回数据流格式,最终会调用AssetManager的openNonAsset方法进行读取资源
                is = res.openRawResource(id, value);
                //2. 根据数据流格式进行解码,在直接加载res资源时,一般opts为空
                bm = decodeResourceStream(res, value, is, null, opts);
            } catch (Exception e) {
    
            } 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;
        }

    我们再来看看BitmapFactory的decodeResourceStream方法

    /**
         * 根据输入的数据流确码成一个新的bitmap, 数据流是从资源处获取,在这里可以根据规则对图片进行一些缩放操作
         */
        public static Bitmap decodeResourceStream(Resources res, TypedValue value,
                InputStream is, Rect pad, Options opts) {
    
            if (opts == null) {//如果没有设置Options,系统会新创建一个Options对象
                opts = new Options();
            }
            //若没有设置opts,inDensity就是初始值0,它代表图片资源密度
            if (opts.inDensity == 0 && value != null) {
                final int density = value.density;
                if (density == TypedValue.DENSITY_DEFAULT) { //如果density等于0,则采用默认值160
                    opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
                } else if (density != TypedValue.DENSITY_NONE) {//如果没有设置资源密度,则图片不会被缩放
                    opts.inDensity = density;//这里density的值对应的就是资源密度值
                }
            }
            //此时inTargetDensity默认也为0
            if (opts.inTargetDensity == 0 && res != null) {
                //将手机的屏幕密度值赋值给最终图片显示的密度
                opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
            }
    
            return decodeStream(is, pad, opts);
        }

    可以看到这里调用了native decodeStream方法:

    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);
            //最终加载的图片的密度
            const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);
            //手机的屏幕密度
            const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
            //如果资源密度不为0,手机屏幕密度也不为0, 资源的密度与屏幕密度不相等时,图片缩放比例=屏幕密度/资源密度,如对于三星手机屏幕密度为640,如果图片放在文件夹为xhdpi 320下,则scale=2,会对图片长宽均放大2倍
            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);//这里+0.5是保证在图片缩小时,可能会出小数,这里加0.5是为了让除后的数向上取整
        scaledHeight = int(scaledHeight * scale + 0.5f);
    }
    if (willScale) {
        const float sx = scaledWidth / float(decodingBitmap.width());
        const float sy = scaledHeight / float(decodingBitmap.height());
    
        // 设置解码图片的colorType
        SkColorType colorType = colorTypeForScaledOutput(decodingBitmap.colorType());
         //设置图片的宽高
        outputBitmap->setInfo(SkImageInfo::Make(scaledWidth, scaledHeight,
                colorType, decodingBitmap.alphaType()));
        if (!outputBitmap->allocPixels(outputAllocator, NULL)) {
            return nullObjectReturn("allocation failed for scaled bitmap");
        }
    
        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);//将图片画到画布上
    }
    ......
    }

    inDensity,inTargetDensity,inScreenDensity, inScaled三者关系

    通过追查代码,我们可以看到图片资源通过数据流解码时,会根据inDensity,inTargetDensity,inScreenDensity三个值和是否被缩放标识inScaled

    • inDensity:图片本身的像素密度(其实就是图片资源所在的哪个密度文件夹下,如在xxhdpi下就是480,如果在asstes、手机内存/sd卡下,默认是160);
    • inTargetDensity:图片最终在bitmap里的像素密度,如果没有赋值,会将inTargetDensity设置成inScreenDensity;
    • inScreenDensity:手机本身的屏幕密度,如我们测试的三星手机dpi=640, 如果inDensity与inTargetDensity不相等时,就需要对图片进行缩放,inScaled = inTargetDensity/inDensity。

    我们上面研究了加载应用程序的图片占用内存大小与手机屏幕密码和图片所放的密度文件夹、图片的编码格式有关,那如果加载的是网络图片或是本地图片,在不同的手机上占用内存又是否一样呢?

    二、加载sd卡下的资源或是网络图片解析

    手机无论是加载sd卡图片,assets路径下还是网络图片,都需要先把图片读成数据流格式,再调用相应的decodeStream方法,将数据流转成bitmap形式,在调用decodeStream如果不设置Options的话,通过以上三款手机打印出图片所占内存大小均为:2994176B,也就是跟手机的屏幕密度没有关系。那如果设置Options中的参数,图片占用的内存会不会与手机的屏幕密度有关系呢?我在测试中发现单独手动设置图片密度inDensity或是inTargetDensity,并不起作用,图片占用内存一直都是图片本身大小。为什么没起作用呢,这需要我们从资源加载的源头看起。

    1. 根据手机本地图片路径获取Bitmap

    我们先来看一下BitmapFactory的decodeFile函数:

    //读取手机本地的图片资源
    public static Bitmap decodeFile(String pathName, Options opts) {
            Bitmap bm = null;
            InputStream stream = null;
            try {
                stream = new FileInputStream(pathName);
                //调用decodeStream将数据流转成bitmap
                bm = decodeStream(stream, null, opts);
            } catch (Exception e) {
                /*  do nothing.
                    If the exception happened on open, bm will be null.
                */
                Log.e("BitmapFactory", "Unable to decode stream: " + e);
            } finally {
                if (stream != null) {
                    try {
                        stream.close();
                    } catch (IOException e) {
                        // do nothing here
                    }
                }
            }
            return bm;
        }

    2. 根据网络地址获取图片Bitmap

    /**
         * 从网络中获取图片,先获取数据流,再转成Bitmap
         * @return
         */
        public Bitmap getBitmapByPicUrl(String picurl) throws IOException {
            InputStream inputStream = null;
            URL url = new URL(picurl);                    //服务器地址
            if (url != null) {
                //打开连接
                HttpURLConnection httpURLConnection = (HttpURLConnection)url.openConnection();
                httpURLConnection.setConnectTimeout(3000);//设置网络连接超时的时间为3秒
                httpURLConnection.setRequestMethod("GET");        //设置请求方法为GET
                httpURLConnection.setDoInput(true);                //打开输入流
                int responseCode = httpURLConnection.getResponseCode();    // 获取服务器响应值
                if (responseCode == HttpURLConnection.HTTP_OK) {        //正常连接
                    inputStream = httpURLConnection.getInputStream();        //获取输入流
                }
            }
            return BitmapFactory.decodeStream(inputStream);
        }

    可以看到通过路径加载图片,最终还是会调用BitmapFactory里的decodeStream方法,我们再来看看decodeStream方法。

    3. 将数据流转成Bitmap

    /**
         *根据输入的数据流确码成一个新的bitmap
         *
         * @param is           从源数据获取的输入数居流,用于解码成bitmap
         * @param outPadding   如果不为空,返回bitmap的边距,这个会加入到图片所占内存大小里
         * @param opts         可以为空; 用来控制图片的采样率和图片是否需要完全解码,还是只需要获取图片大小
         * @return             解码后的图片
         */
        public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
            if (is == null) {
                return null;
            }
    
            Bitmap bm = null;
    
            Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");
            try {//如果数据流来自资源,则直接调用native方法
                if (is instanceof AssetManager.AssetInputStream) {
                    final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
                    bm = nativeDecodeAsset(asset, outPadding, opts);
                } else {
                    bm = decodeStreamInternal(is, outPadding, opts);
                }
    
                if (bm == null && opts != null && opts.inBitmap != null) {
                    throw new IllegalArgumentException("Problem decoding into existing bitmap");
                }
    
                setDensityFromOptions(bm, opts);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
            }
    
            return bm;
        }

    如果数据流来自于资源,则调用BitmapFactory的nativeDecodeAsset,

    private static native Bitmap nativeDecodeAsset(long nativeAsset, Rect padding, Options opts);

    否则调用decodeStreamInternal方法:

     /**
         * is不得为空,会根据流的需要提供一个缓冲区
         */
        private static Bitmap decodeStreamInternal(InputStream is, Rect outPadding, Options opts) {
            // ASSERT(is != null);
            byte [] tempStorage = null;
            if (opts != null) tempStorage = opts.inTempStorage;
            //如果Options没有提供inTempStorage参数会默认提供一个16M的缓冲区
            if (tempStorage == null) tempStorage = new byte[DECODE_BUFFER_SIZE];
            return nativeDecodeStream(is, tempStorage, outPadding, opts);
        }

    此方法会调用native的nativeDecodeStream方法:

                Rect padding, Options opts);

    4. native层的数据流解析

    (1)nativeDecodeStream/nativeDecodeAsset

    通过追踪上述两种nativeDecodeStream方法和nativeDecodeAsset方法,它们最终都会调用nativeDecodeStreamScaled或是nativeDecodeAssetScaled方法,它们会添加两个参数,一个是false,一个是1.0f,这两个参数具体代表什么呢?

    //解码Asset资源的数据流
    static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jint native_asset,
            jobject padding, jobject options) {
        return nativeDecodeAssetScaled(env, clazz, native_asset, padding, options, false, 1.0f);
    }
    //解码纯数据流
    static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
            jobject padding, jobject options) {
        return nativeDecodeStreamScaled(env, clazz, is, storage, padding, options, false, 1.0f);
    }

    (2) nativeDecodeStreamScaled/nativeDecodeAssetScaled

    nativeDecodeAssetScaled或是nativeDecodeStreamScaled方法中最后两个参数,分别是applyScale, sclae,一个是是否申请缩放,一个是缩放比例,也就是从这种数据流加载的图片,默认都不会进缩放。我们注意到,这两个函数最终都会走到doDecode方法里,我们直接看nativeDecodeStreamScaled方法,发现此方法只是对输入流进行了转换,转成SkStream类型。

    static jobject nativeDecodeStreamScaled(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
            jobject padding, jobject options, jboolean applyScale, jfloat scale) {
        jobject bitmap = NULL;
        SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage, 0);
        if (stream) {
            // for now we don't allow purgeable with java inputstreams
            bitmap = doDecode(env, stream, padding, options, false, false, applyScale, scale);
            stream->unref();
        }
        return bitmap;
    }

    (3)doDecode

    我们来看最终的doDecode函数:

    static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
            jobject options, bool allowPurgeable, bool forcePurgeable = false,
            bool applyScale = false, float scale = 1.0f) {
        int sampleSize = 1;
        SkImageDecoder::Mode mode = SkImageDecoder::kDecodePixels_Mode;
        SkBitmap::Config prefConfig = SkBitmap::kARGB_8888_Config;//直接采用ARGB_8888的色彩格式
        bool doDither = true;
        bool isMutable = false;
        bool willScale = applyScale && scale != 1.0f;//上面传的参数applyScale为false,所以willScale为false
        bool isPurgeable = !willScale &&
                (forcePurgeable || (allowPurgeable && optionsPurgeable(env, options)));
        bool preferQualityOverSpeed = false;
        jobject javaBitmap = NULL;
        if (options != NULL) {
            sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);//获取采样率
            if (optionsJustBounds(env, options)) {//是否只加载图片边界,而不解码
                mode = SkImageDecoder::kDecodeBounds_Mode;
            }
           //省略初始化代码
        }
        //省略一堆代码
        SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
        int scaledWidth = decoded->width();//获取解码图片的宽度
        int scaledHeight = decoded->height();//获取解码后图片的调节度
        if (willScale && mode != SkImageDecoder::kDecodeBounds_Mode) {//由于willScale为false,这里不会运行
            scaledWidth = int(scaledWidth * scale + 0.5f);
            scaledHeight = int(scaledHeight * scale + 0.5f);
        }
        // update options (if any)
        if (options != NULL) {
            env->SetIntField(options, gOptions_widthFieldID, scaledWidth);
            env->SetIntField(options, gOptions_heightFieldID, scaledHeight);
            env->SetObjectField(options, gOptions_mimeFieldID,
                    getMimeTypeString(env, decoder->getFormat()));
        }
        if (mode == SkImageDecoder::kDecodeBounds_Mode) {//如果只获取图片大小,这里不会返回bitmap
            return NULL;
        }
    
        if (padding) {//如果设置了padding,则会把边矩算进去
            if (peeker.fPatchIsValid) {
                GraphicsJNI::set_jrect(env, padding,
                        peeker.fPatch->paddingLeft, peeker.fPatch->paddingTop,
                        peeker.fPatch->paddingRight, peeker.fPatch->paddingBottom);
            } else {
                GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1);
            }
        }
        SkPixelRef* pr;
        if (isPurgeable) {
            pr = installPixelRef(bitmap, stream, sampleSize, doDither);
        } else {
            pr = bitmap->pixelRef();
        }
    
    
        return GraphicsJNI::createBitmap(env, bitmap, javaAllocator.getStorageObj(),
                isMutable, ninePatchChunk);//创建bitmap
    }

    通过上面分析直至native中的decode函数,我们发现options里的参数只提取了sampleSize、optionsJustBounds,但是没有见到inDensity,inTargetDensity,inScreenDensity等参数的提取。如果我在加载流前,设置ops.inDensity和ops.inTargetDensity参数如下,图片占用内存大小放大到原来的4倍

    BitmapFactory.Options ops = new BitmapFactory.Options();
                int targetDensity = getResources().getDisplayMetrics().densityDpi;
                ops.inDensity = 240;
                ops.inTargetDensity = 480;
                Bitmap assetsbmp = BitmapFactory.decodeStream(stream, null, ops);

    但是如果只设置inDensity或是inTargetDensity参数,是完全不起作用,感觉是因为只设置了一个参数,另一个参数默认为0, 前面咱们判断过,只要有一个参数为0, 就不会计算缩放比。所以默认还是显示原来图片尺寸大小,只有两个参数均设置,都不为0, 才会去计算缩放比。

    通过上面的分析,我们可以回答最开始的问题了。

    结论:

    1. 在对手机进行屏幕适时,可以只切一套图适配所有的手机。

      • 但是如果只切一套小图,那在高屏幕密度手机上,会对图片进行放大,这样图片占用的内存往往比切相应图片放在高密度文件夹下,占用的内存还要大。

      • 那如果只切一套大图放在高幕文件夹下,在小屏幕密度手机上,会缩小显示,按道理是行得通的。但系统在对图片进行缩放时,会进行大量计算,会对手机的性能有一定的影响。同时如果图片缩放比较狠,可能导致图片出现抖动或是毛边。

      • 所以最好切出不同比便的图片放在不同幕度的文件夹下,对于性能要求不大高的图片,可以只切一套大图;
    2. 一张图片占用内存=图片长 * 图片宽 / (资源图片文件密度/手机屏幕密度)^2 * 每一象素占用字节数,所以图片占用内存跟图片本身大小、手机屏幕密度、图片所在的文件夹密度,图片编码的色彩格式有关;

    3. 对于网络图片,在不同屏幕密度的手机上加载出来,占用内存是一样的。

    4. 对于网络或是assets/手机本地图片加载,如果想通过设置Options里的
      inDensity或是inTargetDensity参数来调整图片的缩放比,必须两个参数均设置才能起作用,只设置一个,不会起作用。

    5. drawable和mipmap文件夹存放图片的区别,首先图片放在drawable-xhdpi和mipmap-xhdpi下,两者占用的内存是一样的,
      Mipmaps早在Android2.2+就可以用了,但是直到4.3 google才强烈建议使用。把图片放到mipmaps可以提高系统渲染图片的速度,提高图片质量,减少GPU压力。其他并没有什么区别。

    展开全文
  • Android图片适应屏幕大小

    千次阅读 2017-08-25 17:17:14
    1.drawable-(hdpi,mdpi,ldpi)的区别 dpi是“dot per inch”的缩写,每英寸像素数。 四种密度分类: ldpi (low), mdpi (medium), hdpi (high), and xhdpi (extra high) 一般情况下的普通屏幕:ldpi是120,mdpi是160...

    1.drawable-(hdpi,mdpi,ldpi)的区别
    dpi是“dot per inch”的缩写,每英寸像素数。
    四种密度分类: ldpi (low), mdpi (medium), hdpi (high), and xhdpi (extra high)
    一般情况下的普通屏幕:ldpi是120,mdpi是160,hdpi是240,xhdpi是320。

    2.WVGA,HVGA,QVGA的区别
    VGA是”Video Graphics Array”,显示标准为640*480。
    WVGA(Wide VGA)分辨率为480*800
    HVGA(Half VGA)即VGA的一半分辨率为320*480
    QVGA(Quarter VGA)即VGA非四分之一分辨率为240*320

    3.drawable-(hdpi,mdpi,ldpi)和WVGA,HVGA,QVGA的联系
    hdpi里面主要放高分辨率的图片,如WVGA (480×800),FWVGA (480×854)
    mdpi里面主要放中等分辨率的图片,如HVGA (320×480)
    ldpi里面主要放低分辨率的图片,如QVGA (240×320)
    系统会根据机器的分辨率来分别到这几个文件夹里面去找对应的图片

    4.如何设置图片

    先为主流的中精度屏幕(HVGA)设计一套icon,确定图片的像素尺寸。为高精度屏幕将图片放大到150%,为低精度屏幕缩小至75%

    将这三套资源放置到程序的三个文件夹下: res/drawable-mdpi/、res/drawable-hdpi/、res/drawable-ldpi/。程序在运行时,平台会根据屏幕的精度调去合适的icon。



    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    android适配各种分辨率的问题

    作者:selfimpr发布于 07月17日 20:03访问(83评论(0

    Android设备屏幕的尺寸是各式各样的,如小米是4英寸的,Xoom平板是10英寸;分辨率也千奇百怪,800×480,960×540等;android版本的碎片化问题更是萦绕于心,不过在设计应用时可以分为两大块:3.0之前的版本和3.0之后的版本。这种情况会带来什么问题我们用三个假设来说明一下。

    1. 假设你的手上有两个4英寸的设备,设备A的分辨率是800×480,设备B的分辨率是1600×960。你在设备A上设计了一个64×64像素的图标,感觉它大小正合适,但放到设备B上的时候,这个图标看上去就只有之前一半大小了。
    2. 假设你手上的两个设备,设备A是4英寸,设备B是10英寸。在设备A上方放了一个tab控件,有三个页签。放到设备B上看时tab控件的三个页签被拉得很长,本来放6个页签的空间只放了三个页签。
    3. 假设你手上的两个设备,设备A装的是Android2.3,设备B装的是Android4.0,而设备B没有menu建,风格也不一样。你发现两个设备上用同一套风格的皮肤并不合适。

    Google提供了一套体系去解决这些问题。我们再回到上面的那张图,drawable文件夹有ldpi、mdpi、hdpi、xhdpi四种。dpi指像素/英寸,而ldpi指120,mdpi指160,hdpi指240,xhdpi指320。小米手机是4英寸、854×480的分辨率,那么小米手机的dpi就是854的平方加480的平方和开2次方后除以4,结果大约是245。如果应用安装在小米手机上,那么系统会调用图中drawable-hdpi里面的资源。这样,你只要做4套资源分别放在drawable-ldpi、drawable-mdpi、drawable-hdpi以及drawable-xdpi下(图标可以按照3:4:6:8的比例制作图片资源),那么就可以解决上面假设1当中提到的问题。

    对于相同dpi、但尺寸不一样的设备,可以通过layout文件控制各种资源的布局。Google将设备分为small(2~3英寸)、normal(4英寸左右)、large(5~7英寸)、xlarge(7英寸以上)。在上面的假设2种,我们可以在layout-normal里配置3个页签的tab栏,在layout-xlarge里配置6个页签的tab栏。如果应用在所有设备上布局都一样,那么就不用考虑针对不同尺寸的layout。从图中那些layout*文件夹可以看出,该应用在hdpi及xhdpi上支持横竖屏,而且横竖屏的布局不一致,但没有考虑不同尺寸的设备使用不同布局的情况。

    Android3.0之前的风格与Android3.0(包含3.0)之后的风格区别很大,图中那个应用就使用了两种风格的资源及布局。Android2.3的小米会使用drawable-hdpi及layout-hdpi当中的文件,而Android4.0的小米就会使用drawable-hdpi-v11及layout-hdpi-v11里面的文件。

    //-------------------------------------------------------------------------------------------------------------------------------------------------------------
    一:不同的layout

    Android手机屏幕大小不一,有480x320, 640x360, 800x480.怎样才能让App自动适应不同的屏幕呢?
    其实很简单,只需要在res目录下创建不同的layout文件夹,比如layout-640x360,layout-800x480,所有的layout文件在编译之后都会写入R.Java里,而系统会根据屏幕的大小自己选择合适的layout进行使用。

    二:hdpi、mdpi、ldpi

    在之前的版本中,只有一个drawable,而2.1版本中有drawable-mdpi、drawable-ldpi、drawable-hdpi三个,这三个主要是为了支持多分辨率。

      drawable- hdpi、drawable- mdpi、drawable-ldpi的区别:

      (1)drawable-hdpi里面存放高分辨率的图片,如WVGA (480x800),FWVGA (480x854)

      (2)drawable-mdpi里面存放中等分辨率的图片,如HVGA (320x480)

      (3)drawable-ldpi里面存放低分辨率的图片,如QVGA (240x320)

      系统会根据机器的分辨率来分别到这几个文件夹里面去找对应的图片。

    更正:应该是对应不同density 的图片

      在开发程序时为了兼容不同平台不同屏幕,建议各自文件夹根据需求均存放不同版本图片。

    [i]备注:三者的解析度不一样,就像你把电脑的分辨率调低,图片会变大一样,反之分辨率高,图片缩小。 [/i]
    屏幕方向:

    横屏竖屏自动切换:

    1
     可以在res目录下建立layout-port-800x600和layout-land两个目录,里面分别放置竖屏和横屏两种布局文件,这样在手机屏幕方向变化的时候系统会自动调用相应的布局文件,避免一种布局文件无法满足两种屏幕显示的问题。
    

    不同分辨率横屏竖屏自动切换:

    以800x600为例
    可以在res目录下建立layout-port-800x600和layout-land-800x600两个目录

    不切换:

    以下步骤是网上流传的,不过我自己之前是通过图形化界面实现这个配置,算是殊途同归,有空我会把图片贴上来。

    还要说明一点:每个activity都有这个属性screenOrientation,每个activity都需要设置,可以设置为竖屏(portrait),也可以设置为无重力感应(nosensor)。

    要让程序界面保持一个方向,不随手机方向转动而变化的处理办法:

    在AndroidManifest.xml里面配置一下就可以了。加入这一行android:screenOrientation="landscape"。
    例如(landscape是横向,portrait是纵向):

    Java代码:

    <?xml version="1.0" encoding="utf-8"?>











    另外,android中每次屏幕的切换动会重启Activity,所以应该在Activity销毁前保存当前活动的状态,在Activity再次Create的时候载入配置,那样,进行中的游戏就不会自动重启了!

    有的程序适合从竖屏切换到横屏,或者反过来,这个时候怎么办呢?可以在配置Activity的地方进行如下的配置android:screenOrientation="portrait"。这样就可以保证是竖屏总是竖屏了,或者landscape横向。

    而有的程序是适合横竖屏切换的。如何处理呢?首先要在配置Activity的时候进行如下的配置:android:configChanges="keyboardHidden|orientation",另外需要重写Activity的 onConfigurationChanged方法。实现方式如下,不需要做太多的内容:

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
    // land do nothing is ok
    } else if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
    // port do nothing is ok
    }
    }

    写一个支持多分辨的程序,基于1.6开发的,建立了三个资源文件夹drawable-hdpi drawable-mdpi drawable-ldpi,里面分别存放72*72 48*48 36*36的icon图标文件。当我在G1(1.5的系统)上测试时,图标应该自适应为48*48才对啊,但实际显示的是36*36。怎么才能让其自适应 48*48的icon图标呢

    解决办法 drawable-hdpi drawable-mdpi drawable-ldpi改成drawable-480X320 drawable-800X480的多分辨支持的文件夹

    对于Android游戏开发我们不得不像iPhone那样思考兼容 Android平板电脑,对于苹果要考虑iPad、iPhone 3GS和iPhone 4等屏幕之间的兼容性,对于几乎所有的分辨率总结了大约超过20中粉笔阿女郎的大小和对应关系,对于开发Android游戏而言可以考虑到未来的3.0以及很多平板电脑的需要。

    常规的我们可能只考虑QVGA,HVGA,WVGA,FWVGA和DVGA,但是抛去了手机不谈,可能平板使用类似WSVGA的1024×576以及WXGA的1280×768等等。
    QVGA = 320 * 240;
    WQVGA = 320 * 480;
    WQVGA2 = 400 * 240;
    WQVGA3 = 432 * 240;
    HVGA = 480 * 320;
    VGA = 640 * 480;
    WVGA = 800 * 480;
    WVGA2 = 768 * 480;
    FWVGA = 854 * 480;
    DVGA = 960 * 640;
    PAL = 576 * 520;
    NTSC = 486 * 440;
    SVGA = 800 * 600;
    WSVGA [...]

    这是一个比较有代表性的Android软件资源包,drawable里面存放的是应用的图标文件,layout存放的是布局,简单说就是这些图标如何摆放。为什么Android上需要这么多资源包文件和布局文件是我们接下来需要讨论的问题。

    Android设备屏幕的尺寸是各式各样的,如小米是4英寸的,Xoom平板是10英寸;分辨率也千奇百怪,800×480,960×540等;Android版本的碎片化问题更是萦绕于心,不过在设计应用时可以分为两大块:3.0之前的版本和3.0之后的版本。这种情况会带来什么问题我们用三个假设来说明一下。

    1. 假设你的手上有两个4英寸的设备,设备A的分辨率是800×480,设备B的分辨率是1600×960。你在设备A上设计了一个64×64像素的图标,感觉它大小正合适,但放到设备B上的时候,这个图标看上去就只有之前一半大小了。

    2. 假设你手上的两个设备,设备A是4英寸,设备B是10英寸。在设备A上方放了一个tab控件,有三个页签。放到设备B上看时tab控件的三个页签被拉得很长,本来放6个页签的空间只放了三个页签。

    3. 假设你手上的两个设备,设备A装的是Android2.3,设备B装的是Android4.0,而设备B没有menu建,风格也不一样。你发现两个设备上用同一套风格的皮肤并不合适。

    Google提供了一套体系去解决这些问题。我们再回到上面的那张图,drawable文件夹有ldpi、mdpi、hdpi、xhdpi四种。dpi指像素/英寸,而ldpi指120,mdpi指160,hdpi指240,xhdpi指320。小米手机是4英寸、854×480的分辨率,那么小米手机的dpi就是854的平方加480的平方和开2次方后除以4,结果大约是245。如果应用安装在小米手机上,那么系统会调用图中drawable-hdpi里面的资源。这样,你只要做4套资源分别放在drawable-ldpi、drawable-mdpi、drawable-hdpi以及drawable-xdpi下(图标可以按照3:4:6:8的比例制作图片资源),那么就可以解决上面假设1当中提到的问题。

    对于相同dpi、但尺寸不一样的设备,可以通过layout文件控制各种资源的布局。Google将设备分为small(2~3英寸)、normal(4英寸左右)、large(5~7英寸)、xlarge(7英寸以上)。在上面的假设2种,我们可以在layout-normal里配置3个页签的tab栏,在layout-xlarge里配置6个页签的tab栏。如果应用在所有设备上布局都一样,那么就不用考虑针对不同尺寸的layout。从图中那些layout*文件夹可以看出,该应用在hdpi及xhdpi上支持横竖屏,而且横竖屏的布局不一致,但没有考虑不同尺寸的设备使用不同布局的情况。

    from http://blog.csdn.net/r8hzgemq/article/details/8243119

    声明:eoe文章著作权属于作者,受法律保护,转载时请务必以超链接形式附带如下信息

    原文作者: selfimpr

    原文地址: http://my.eoe.cn/964494/archive/5704.htm

    展开全文
  • 图片相关内容总结

    图片是我们app开发中最常见的一种展示形态,因此对于图片的压缩和内存空间占用是非常有必要理解和掌握的,这篇文章借鉴和归纳了一下图片加载到内存空间的占用及常见压缩算法等,通过这样的总结和归纳希望我们再次面对图片问题的时候有一个很明朗的思维和处理方案。

    图片的物理内存的计算

    对于一张图片,无论是jpg、png、bmp或者其他格式,我们光知道他们像素总数是1280*720,是无法计算出图片大小的~~因为压缩方式、编码等都不一样.
    但是我们要有一个这样的概念
    bmp 无压缩情况下的原图
    png 无损压缩算法格式
    jpg 有损压缩算法格式
    这里我们暂时不过多讨论物理内存占用情况,android也提供了webp的有损压缩格式很大程度上减小了物理内存的空间占用。这里我们特别的研究一下运行时内存占用及优化方案。

    图片运行内存计算

    我们常规的认知里计算图片占用运行时虚拟机内存的计算方法

    图片分辨率 * 每个像素点大小

    而每个像素点的大小的计算方式是和图片质量有关系如下表所示

    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. 参考GoogleDeveloper

    BitMap.Config释义
    ALPHA_8此时图片只有alpha值,没有RGB值,一个像素占用一个字节
    ARGB_4444一个像素占用2个字节,alpha(A)值,Red(R)值,Green(G)值,Blue(B)值各占4个bites,共16bites,即2个字节
    ARGB_8888一个像素占用4个字节,alpha(A)值,Red(R)值,Green(G)值,Blue(B)值各占8个bites,共32bites,即4个字节这是一种高质量的图片格式,电脑上普通采用的格式。它也是Android手机上一个BitMap的默认格式。
    RGB_565从Android4.0开始,该选项无效。即使设置为该值,系统任然会采用 ARGB_8888来构造图片

    ?那么一张图片无论jpg或者png加载到内存中的时候占用的内存空间是分辨率*Bitmap.Config占用的字节数吗
    是的,这个答案是不准确的。

    如果我们要获取这个答案的话我们可以自己写demo去测试,分别加载不同资源目录下如assests;res下不同屏幕密度下的资源文件夹如drawable-hdpi,drawable-xdpi;磁盘目录下等 具体的测试过程可以参考这篇文章

    private void loadResImage(ImageView imageView) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.weixin, options);
        //Bitmap bitmap = BitmapFactory.decodeFile("mnt/sdcard/weixin.png", options);
        imageView.setImageBitmap(bitmap);
        Log.i("!!!!!!", "bitmap:ByteCount = " + bitmap.getByteCount() + ":::bitmap:AllocationByteCount = " + bitmap.getAllocationByteCount());
        Log.i("!!!!!!", "width:" + bitmap.getWidth() + ":::height:" + bitmap.getHeight());
        Log.i("!!!!!!", "inDensity:" + options.inDensity + ":::inTargetDensity:" + options.inTargetDensity);
        Log.i("!!!!!!", "imageview.width:" + imageView.getWidth() + ":::imageview.height:" +    imageView.getHeight());
    }
    

    这里我们只备注一下测试结果:
    当我们把图片放在不同位置去分别加载的时候,得到的在内存中占用大小是不一样的如

    资源文件夹对应像素密度值
    mdpi160
    hdpi240
    xhdpi320
    xxhdpi480

    结论如下(当我们测试一张1024*768分辨率的图片并且图片质量为ARGB_8888也就是像素占用字节数为4)

    • 图片放在assests和磁盘里加载的话占用内存大小计算方式为10247684
    • 图片放在不同密度对应的文件夹时占用内存大小为1024密度值/260768密度值/2604
      我们看到在不同密度值代表的文件夹的图片会经过一次分辨率转换 然后在进行图片加载;这些理论是基于Android原生的图片处理算法得出的,如果使用了第三方的图片加载库,这些理论就有可能不成立了,因为他们对不同的图片进行了相应的图片压缩算法的处理。如文章最后我们要说的一个图片压缩算法一样。

    优化方案

    基于以上的知识储备,因此理解图片加载的优化方案就很好理解我们可以通过以下几个方案来处理图片优化相关

    • 存放在apk中的资源文件如果是大文件的话可以考虑使用jpg格式替换png格式;
    • 加载资源文件res不是只放一份文件就可以,如果需要兼容不同的设备就需要放置不同的文件防止造成图片加载到内存空间占用过大
    • 处理图片的分辨率或者图片质量达到兼容视觉和内存的效果
    • google推荐使用Glide处理图片缓存问题,但是这些基础的图片处理策略我们还是要清楚

    jpg图片压缩算法

    首先明确一下这里的压缩算法,针对的都是对于图片存储的物理空间大小的算法。对于图片的压缩不一定能改变其在内存空间占用的大小(具体参考上面篇幅)

    android 原生的图片压缩算法是基于一个叫skia开源的引擎(基于JPEG引擎修改),但是去掉了比较消耗cpu的哈夫曼算法;这样处理的结果是减少了cpu的消耗但是图片处理的效果并不一定是最好的,改为定长算法 导致了拍一张高清图片会占用很大的存储空间。这也还是计算机领域中存储空间和性能的博弈;备注:android原生的图片处理引擎解码目前依然保留的是哈夫曼算法

    使用Android系统压缩算法api

    质量压缩

    质量压缩是保持像素的前提下改变图片的位深及透明度,(即:通过算法抠掉(同化)了图片中的一些某个些点附近相近的像素),达到降低质量压缩文件大小的目的。常用在不需要直接操作原图的一些场景中,如 上传图片,图片另存

    使用原生api的进行质量压缩一些示例:

     /**
         * 质量压缩方法
         *
         * @param image
         * @return
         */
        public static Bitmap compressImage(Bitmap image) {
    
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            image.compress(Bitmap.CompressFormat.JPEG, 100, baos);//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
            int options = 100;
            while (baos.toByteArray().length / 1024 > 100) {  //循环判断如果压缩后图片是否大于100kb,大于继续压缩
                baos.reset();//重置baos即清空baos
                //第一个参数 :图片格式 ,第二个参数: 图片质量,100为最高,0为最差  ,第三个参数:保存压缩后的数据的流
                image.compress(Bitmap.CompressFormat.JPEG, options, baos);//这里压缩options%,把压缩后的数据存放到baos中
                options -= 10;//每次都减少10
            }
            ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());//把压缩后的数据baos存放到ByteArrayInputStream中
            Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);//把ByteArrayInputStream数据生成图片
            return bitmap;
        }
    
    
     /**
         * 质量压缩
         * 设置bitmap options属性,降低图片的质量,像素不会减少
         * 第一个参数为需要压缩的bitmap图片对象,第二个参数为压缩后图片保存的位置
         * 设置options 属性0-100,来实现压缩
         *
         * @param bmp
         * @param file
         */ public static void qualityCompress(Bitmap bmp, File file) {
              // 0-100 100为不压缩
             int quality = 20;
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
             // 把压缩后的数据存放到baos中
            bmp.compress(Bitmap.CompressFormat.JPEG, quality, baos);
            try {
                FileOutputStream fos = new FileOutputStream(file);
                fos.write(baos.toByteArray());
                fos.flush();
                fos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
         }
    
    
    

    尺寸压缩

    属于一种减少了像素的压缩方式,达到一种缩略图片的效果,常用在 加载缩略图

    使用原生api的进行质量压缩一些示例:

     /**
         * 压缩图片使用,采用BitmapFactory.decodeFile。这里是尺寸压缩
         * @param context
         * @param imageUri
         * @return
         */
        public Bitmap bitmapFactory(Context context,Uri imageUri){
            String[] filePathColumns = {MediaStore.Images.Media.DATA};
            Cursor c = context.getContentResolver().query(imageUri, filePathColumns, null, null, null);
            c.moveToFirst();
            int columnIndex = c.getColumnIndex(filePathColumns[0]);
            String imagePath = c.getString(columnIndex);
            c.close();
    
            // 配置压缩的参数
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true; //获取当前图片的边界大小,而不是将整张图片载入在内存中,避免内存溢出
            BitmapFactory.decodeFile(imagePath, options);
            options.inJustDecodeBounds = false;
            inSampleSize的作用就是可以把图片的长短缩小inSampleSize倍,所占内存缩小inSampleSize的平方
            options.inSampleSize = caculateSampleSize(options,500,50);
            Bitmap bm = BitmapFactory.decodeFile(imagePath, options); // 解码文件
             return  bm;
        }
    
        /**
         * 计算出所需要压缩的大小
         * @param options
         * @param reqWidth  我们期望的图片的宽,单位px
         * @param reqHeight 我们期望的图片的高,单位px
         * @return
         */
        private int caculateSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
            int sampleSize = 1;
            int picWidth = options.outWidth;
            int picHeight = options.outHeight;
            if (picWidth > reqWidth || picHeight > reqHeight) {
                int halfPicWidth = picWidth / 2;
                int halfPicHeight = picHeight / 2;
                while (halfPicWidth / sampleSize > reqWidth || halfPicHeight / sampleSize > reqHeight) {
                    sampleSize *= 2;
                }
            }
            return sampleSize;
        }
    
    
    
    

    采样率压缩

    采样率压缩是改变了图片的像素,他是通过先读取图片的边,然后在自己设定图片的边,然后根据设定,读取图片的像素。是图片压缩常用到的一种处理方案因为他会只把目标尺寸加载进内存这样能一定程度上降低oom的可能性

    使用原生api的进行质量压缩一些示例:

    public static Bitmap compressImageToBitmap(String imagePath){
    		BitmapFactory.Options options = new BitmapFactory.Options();
    		//只计算图片的边界不降图片实体加载进内存
    		options.inJustDecodeBounds = true;
    		Bitmap bitmap = BitmapFactory.decodeFile(imagePath,options);
    		//加载真实的图片内容进入内存
    		options.inJustDecodeBounds = false;
    		//降低图片占用空间为原图片的1/4;宽高都减小了一倍
    		options.inSampleSize = 2;
    		bitmap = BitmapFactory.decodeFile(imagePath,options);
    		return bitmap;
    

    webp图片压缩

    WebP(发音:weppy)是一种同时提供了有损压缩与无损压缩(可逆压缩)的图片文件格式,派生自影像编码格式VP8,被认为是WebM多媒体格式的姊妹项目,是由Google在购买On2 Technologies后发展出来,以BSD授权条款发布。

    这里不做过多解释,Google推荐使用,压缩效果也比较明显可以作为一种减小压缩包的一种尝试,android 4.4以后完全兼容

    使用自定义压缩算法

    在上面我们讲到了android为了减少cpu性能消耗,删减了图片处理引擎的哈夫曼算法,这里我们就可以根据这个原理直接使用libjpeg库,使用哈夫曼算法替代原生的定长算法来达到最佳图片压缩;当然这里我们可以根据机型来控制.对于一些低端机型还是建议使用原生的方法来兼容性能的平衡

    自定义压缩算法的核心还是libjpeg库
    libjpeg下载地址

    每一个像素都有三个信息RGB这里通过计算Bitmap的rgb值保存到一维数组;然后通过使用jpeg引擎的哈夫曼算法来优化图片压缩的程度。

    核心代码

    
    #include "bitherlibjni.h"
    #include <string.h>
    #include <android/bitmap.h>
    #include <android/log.h>
    #include <stdio.h>
    #include <setjmp.h>
    #include <math.h>
    #include <stdint.h>
    #include <time.h>
    
    //统一编译方式
    extern "C" {
    #include "jpeg/jpeglib.h"
    #include "jpeg/cdjpeg.h"		/* Common decls for cjpeg/djpeg applications */
    #include "jpeg/jversion.h"		/* for version message */
    #include "jpeg/android/config.h"
    }
    
    
    #define LOG_TAG "jni"
    #define LOGW(...)  __android_log_write(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
    #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
    #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
    
    #define true 1
    #define false 0
    
    typedef uint8_t BYTE;
    
    char *error;
    struct my_error_mgr {
      struct jpeg_error_mgr pub;
      jmp_buf setjmp_buffer;
    };
    
    typedef struct my_error_mgr * my_error_ptr;
    
    METHODDEF(void)
    my_error_exit (j_common_ptr cinfo)
    {
      my_error_ptr myerr = (my_error_ptr) cinfo->err;
      (*cinfo->err->output_message) (cinfo);
      error=(char*)myerr->pub.jpeg_message_table[myerr->pub.msg_code];
      LOGE("jpeg_message_table[%d]:%s", myerr->pub.msg_code,myerr->pub.jpeg_message_table[myerr->pub.msg_code]);
     // LOGE("addon_message_table:%s", myerr->pub.addon_message_table);
    //  LOGE("SIZEOF:%d",myerr->pub.msg_parm.i[0]);
    //  LOGE("sizeof:%d",myerr->pub.msg_parm.i[1]);
      longjmp(myerr->setjmp_buffer, 1);
    }
    
    int generateJPEG(BYTE* data, int w, int h, int quality,
    		const char* outfilename, jboolean optimize) {
    
    	//jpeg的结构体,保存的比如宽、高、位深、图片格式等信息,相当于java的类
    	struct jpeg_compress_struct jcs;
    
    	//当读完整个文件的时候就会回调my_error_exit这个退出方法。setjmp是一个系统级函数,是一个回调。
    	struct my_error_mgr jem;
    	jcs.err = jpeg_std_error(&jem.pub);
    	jem.pub.error_exit = my_error_exit;
    	if (setjmp(jem.setjmp_buffer)) {
    		return 0;
    	}
    
    	//初始化jsc结构体
    	jpeg_create_compress(&jcs);
    	//打开输出文件 wb:可写byte
    	FILE* f = fopen(outfilename, "wb");
    	if (f == NULL) {
    		return 0;
    	}
    	//设置结构体的文件路径
    	jpeg_stdio_dest(&jcs, f);
    	jcs.image_width = w;//设置宽高
    	jcs.image_height = h;
    
    	//看源码注释,设置哈夫曼编码:/* TRUE=arithmetic coding, FALSE=Huffman */
    	jcs.arith_code = false;
    	int nComponent = 3;
    	/* 颜色的组成 rgb,三个 # of color components in input image */
    	jcs.input_components = nComponent;
    	//设置结构体的颜色空间为rgb
    	jcs.in_color_space = JCS_RGB;
    
    	//全部设置默认参数/* Default parameter setup for compression */
    	jpeg_set_defaults(&jcs);
    	//是否采用哈弗曼表数据计算 品质相差5-10倍
    	jcs.optimize_coding = optimize;
    	//设置质量
    	jpeg_set_quality(&jcs, quality, true);
    	//开始压缩,(是否写入全部像素)
    	jpeg_start_compress(&jcs, TRUE);
    
    	JSAMPROW row_pointer[1];
    	int row_stride;
    	//一行的rgb数量
    	row_stride = jcs.image_width * nComponent;
    	//一行一行遍历
    	while (jcs.next_scanline < jcs.image_height) {
    		//得到一行的首地址
    		row_pointer[0] = &data[jcs.next_scanline * row_stride];
    
    		//此方法会将jcs.next_scanline加1
    		jpeg_write_scanlines(&jcs, row_pointer, 1);//row_pointer就是一行的首地址,1:写入的行数
    	}
    	jpeg_finish_compress(&jcs);//结束
    	jpeg_destroy_compress(&jcs);//销毁 回收内存
    	fclose(f);//关闭文件
    
    	return 1;
    }
    
    /**
     * byte数组转C的字符串
     */
    char* jstrinTostring(JNIEnv* env, jbyteArray barr) {
    	char* rtn = NULL;
    	jsize alen = env->GetArrayLength( barr);
    	jbyte* ba = env->GetByteArrayElements( barr, 0);
    	if (alen > 0) {
    		rtn = (char*) malloc(alen + 1);
    		memcpy(rtn, ba, alen);
    		rtn[alen] = 0;
    	}
    	env->ReleaseByteArrayElements( barr, ba, 0);
    	return rtn;
    }
    
    jstring Java_net_bither_util_NativeUtil_compressBitmap(JNIEnv* env,
    		jclass thiz, jobject bitmapcolor, int w, int h, int quality,
    		jbyteArray fileNameStr, jboolean optimize) {
    	BYTE *pixelscolor;
    	//1.将bitmap里面的所有像素信息读取出来,并转换成RGB数据,保存到二维byte数组里面
    	//处理bitmap图形信息方法1 锁定画布
    	AndroidBitmap_lockPixels(env,bitmapcolor,(void**)&pixelscolor);
    
    	//2.解析每一个像素点里面的rgb值(去掉alpha值),保存到一维数组data里面
    	BYTE *data;
    	BYTE r,g,b;
    	data = (BYTE*)malloc(w*h*3);//每一个像素都有三个信息RGB
    	BYTE *tmpdata;
    	tmpdata = data;//临时保存data的首地址
    	int i=0,j=0;
    	int color;
    	for (i = 0; i < h; ++i) {
    		for (j = 0; j < w; ++j) {
    			//解决掉alpha
    			//获取二维数组的每一个像素信息(四个部分a/r/g/b)的首地址
    			color = *((int *)pixelscolor);//通过地址取值
    			//0~255:
    //			a = ((color & 0xFF000000) >> 24);
    			r = ((color & 0x00FF0000) >> 16);
    			g = ((color & 0x0000FF00) >> 8);
    			b = ((color & 0x000000FF));
    			//改值!!!----保存到data数据里面
    			*data = b;
    			*(data+1) = g;
    			*(data+2) = r;
    			data = data + 3;
    			//一个像素包括argb四个值,每+4就是取下一个像素点
    			pixelscolor += 4;
    		}
    	}
    	//处理bitmap图形信息方法2 解锁
    	AndroidBitmap_unlockPixels(env,bitmapcolor);
    	char* fileName = jstrinTostring(env,fileNameStr);
    	//调用libjpeg核心方法实现压缩
    	int resultCode = generateJPEG(tmpdata,w,h,quality,fileName,optimize);
    	if(resultCode ==0){
    		jstring result = env->NewStringUTF("-1");
    		return result;
    	}
    	return env->NewStringUTF("1");
    }
    
    

    扩展

    mipmap文件夹和drawable文件夹下的图片文件放置规则
    参考google官网支持不同的像素密度

    将应用图标放在 mipmap 目录中
    与其他所有位图资源一样,对于应用图标,您也需要提供特定于密度的版本。不过,某些应用启动器显示的应用图标会比设备的密度级别所要求的大差不多 25%。

    例如,如果设备的密度级别为 xxhdpi 且您提供的最大应用图标在 drawable-xxhdpi 中,那么启动器应用会放大此图标,这会使其看起来不太清晰。因此,您应在 mipmap-xxxhdpi 目录中提供一个密度更高的启动器图标,而后启动器便可改用 xxxhdpi 资源。

    由于应用图标可能会像这样放大,因此您应将所有应用图标都放在 mipmap 目录中,而不是放在 drawable 目录中。与 drawable 目录不同,所有 mipmap 目录都会保留在 APK 中,即使您构建特定于密度的 APK 也是如此。这样,启动器应用便可选取要显示在主屏幕上的最佳分辨率图标。

    上面三段描述的都是启动图标,那么平时使用的图片资源文件应该放在哪里呢,经验之谈还是放在drawable文件夹下区分不同的屏幕密度,启动图标放在mipmap下区分不同的屏幕密度

    • 放在mipmap的图片文件和放在drawable相同层级文件夹的图片占用运行时内存空间一致没有节省内存的作用 ,因此这里不做区分关键
    • 使用mipmap的资源图片会和设计稿存一定的误差,具体可能和mipmap的压缩算法有一定关系可研究mipmap纹理过滤
    • 在App中,无论你将图片放在drawable还是mipmap目录,系统只会加载对应density中的图片。而在Launcher中,如果使用mipmap,那么Launcher会自动加载更加合适的密度的资源,所以启动图标一定要区分适配不同屏幕密度的分辨率并放置在对应的目录里放置在launcher中展示失真

    【参考文档1】https://www.jianshu.com/p/e49ec7d053b3
    【参考文档2】https://developer.android.com/reference/android/graphics/Bitmap.Config.html
    【可借鉴源码1】鲁班库
    【可借鉴源码2】Compressor
    【在线处理png】TinyPng

    展开全文
  • 背景:有天接到个小游戏,里面有个部分是 起来找茬,开始准备用设计给的坐标来写,但是发现好像不太符合程序员爱钻研的精神,于是就想着做个能自动识别的,几经周折,后来决定用 canvas的像素来处理这个...
  • 第六章 图片

    千次阅读 2019-09-26 16:13:09
    文章目录从网络加载个10M的图片图加载),说下注意事项?有关Bitmap导致OOM的原因知道吗?如何优化?说一下三级缓存的原理?说说你平常会使用的一些第三方图片加载库,最好给我谈谈它的原理?Glide源码分析?...
  • Android性能优化()--图片优化

    千次阅读 2020-02-06 00:14:25
    1.当图片存放在 res 资源目录,图片占用内存大小 = width * scale * height * scale * 个像素所占内存大小 ,scale = inTargetDensity / density 2.当图片存放在磁盘空间,图片占用内存大小 = width * height * ...
  • 最近在做李宁的个项目,要求特别高,所以今天记录一下自己遇到的难点,希望对初学者有用!...public Bitmap doBitmapFile(InputStream in, boolean isFlag) {// isFlag是个标志 true表示是图 false小图 B
  • (注寸相:如护照,签证申请等,以级学位证书采用的是大一寸,48毫米×33毫米。而身份证,体检表,等采用小寸32毫米×22毫米,第二代身份 证 (26mm×32mm),普通寸相则25mm×35mm。护照旅行证件的相片标准...
  • 在这篇之前的内容是《Factor Analysis》,由于非常理论,打算学完整个课程后再写。在写这篇之前,我阅读了PCA、SVD和LDA。这几个模型相近,却都有自己的特点。本篇打算先介绍PCA,至于他们之间的关系,只能是边学...
  • 相似图片搜索原理和JAVA代码实现

    千次阅读 2019-04-04 09:49:35
    相似图片搜索原理() 相似图片搜索java代码实现 相似图片搜索原理(二) 相似图片搜索原理() 你可以用一张图片,搜索互联网上所有与它相似的图片。点击搜索框中照相机的图标。 个对话框会出现。 ...
  • python 处理图片像素点(1)

    千次阅读 2018-08-21 19:09:24
    在做爬虫的时候有时需要识别验证码,但是验证码一般都有干扰物,这时需要对验证码进行...img = Image.open('C:/img.jpg').convert('L') #打开图片,convert图像类型有L,RGBA # 转化为黑白图 def blackWrite(img): bl...
  • 全世界有3.14 % 的人已经关注了数据与算法之美很搜索引擎可以用一张图片,搜索互联网上所有与它相似的图片。你输入网片的网址,或者直接上传图片,Google就会找出与其...
  • 图片相似算法(1

    万次阅读 2016-08-21 11:36:14
    如果是个二值灰度图象,它的象素值只能为0或1,我们说它的灰度级为2。用个例子来说明吧:个256级灰度的图象,如果RGB三个量相同时,如:RGB(100,100,100)就代表灰度为100,RGB(50,50,50)代表灰度为50。 彩色图象...
  • 篇文章让你知道ZXing是怎么知道图片中是否有二维码的。
  • 深入安卓大图片处理机制 本地及网络图片不加载到内存预压缩
  • 科技论文中图片的处理方法

    万次阅读 2017-01-03 09:39:32
    、科技论文中图片的处理方法 科技论文中图片的质量非常重要,图片质量的好坏一定程度上决定了论文能否被录用。处理一张图片如果没有使用正确的方法,即使花费大量的时间也无法得到理想的效果。为此,本文简要...
  • 在深度学习中,将图片输入网络进行训练之前一般都会对图片进行标准化,以加速训练过程和拟合过程。 注意:标准化操作中的均值与标准差是从训练集算出来的,然后用于训练集/验证集/测试集的标准化。 python代码如下:...
  • 1.背景 先看下百度百科的介绍:最小二乘法(又称最小平方法)是种数学优化技术。它通过最小化误差的平方和寻找数据的最佳函数匹配。利用最小二乘法可以简便地求得未知的数据,并使得这些求得的数据与实际数据之间...
  • Python 比较俩张图片差异

    千次阅读 2020-05-02 22:20:38
    参考:How-To: Python Compare Two Images 对比俩张图片差异,可以用均方...尽管MSE的计算速度要快得,但它的主要缺点是(1)全局应用,(2)仅估计图像的感知错误。 另方面,SSIM虽然速度较慢,但​​可以通过...
  • ImageView制作个显示图片的activity,类似相册那样...但是遇到了个问题,就是图片的时候(也没多大,也就是手机摄像头拍的照片,4128x3096,13M像素),真机运行会显示空白,什么都没有 但是模拟器运行正常,就
  • Android 图片压缩的三种方法

    千次阅读 2020-09-02 16:42:50
    随着Android手机的越来越先进,摄像头也越来越清晰,但是给我们开发者而言传递的图片也是越来越,这个时候我们可以对一些没有必要原图展示的图片进行压缩,今天分享下常用的三种方法 第种,大小压缩 第二种,...
  • 1、 比如拿到个汽车的样本,里面既有以“千米/每小时”度量的最大速度特征,也有“英里/小时”的最大速度特征,显然这两个特征有个多余。 2、 拿到个数学系的本科生期末考试成绩单,里面有三
  • 建模方法(四)-因子分析定义和应用

    万次阅读 多人点赞 2018-08-20 20:58:05
    因子分析(factor analysis)也是种降维、简化数据的技术。 它通过研究众多变量之间的内部依赖关系,使用少数几个“抽象”的变量来表示其基本的 数据结构。这几个抽象的变量被称作“因子”,能反映原来 众多变量的...
  • java实现拖动图片验证码

    千次阅读 2018-05-15 21:01:38
    思路是,对原图产生两张图片,一张是底图,被抠掉部分的图片,另外一张是移动图,被抠出的来部分 只写了后台怎么生成拖动验证码的两个图片,前端的还没写,待续。以下是后台的代码 [code="java"] /...
  • 先来个效果图: 使用技术主要是flex布局,绝对定位布局,小程序前端页面开发,以及一些样式! 直接贴代码,都有详细注释,熟悉一下,方便...-- 顶部推荐图片轮播 --> <view class='selection'> <sw...
  • Unity 2D图片外轮廓描边和内发光的Shader实现

    万次阅读 多人点赞 2018-09-18 23:19:54
    很长时间没有写博客了,方面Sebastian大佬正在更新程序生成...找了很资料一直都没有找到实现起来比较不错的方法,而且搜出来的部分都是3D的描边,3D描边的思想很容易掌握,但是一直没有2D的外轮廓的描边,所...
  • 图片压缩的几种方式

    千次阅读 2020-11-17 18:44:19
    1、设置图片格式 Android目前常用的图片格式有png,jpeg和webp, png:无损压缩图片格式,支持Alpha通道,Android切图素材采用此格式 jpeg:有损压缩图片格式,不支持背景透明,适用于照片等色彩丰富的图压缩...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 29,689
精华内容 11,875
关键字:

一平方是多大的图片