精华内容
参与话题
问答
  • 图片优化

    2011-11-01 14:34:18
    最近在Widget开发中,发现有一些PNG图片在E72手机上显示不正常,原本怀疑是手机的问题。但是我今天把图片重新用Photoshop保存了一下之后,显示就正常了。可能是美工在制作图片的时候没有考虑手机的一些特殊需求。 ...


    最近在Widget开发中,发现有一些PNG图片在E72手机上显示不正常,原本怀疑是手机的问题。但是我今天把图片重新用Photoshop保存了一下之后,显示就正常了。可能是美工在制作图片的时候没有考虑手机的一些特殊需求。

    [编辑]基本概念

    在Photoshop里面编辑图片的时候一般都是使用默认的真彩色模式(图像->模式->RGB颜色),保存的PSD文件也都是用这种模式,优点是所有图像的颜色信息都能保存,缺点是文件尺寸比较大。如果大家都使用PSD文件格式,那世界就完美了,不存在这么多麻烦的事情。但是,在现实世界里,因为手机或者互联网的速度有限,不能让用户下载过大的图像文件,所以一般网页上或者手机软件上要求PNG或者JPG或者GIF一类的压缩图像文件。这些文件是压缩过的,有可能会造成图像质量下降,但好处是文件尺寸小。所以,美工的一个非常大的职责就是尽可能平衡图片质量与文件大小之间的关系,用尽可能小的文件尺寸展现尽可能好的图片质量。影响图片质量和文件大小的几个关键因素是颜色数、格式和压缩方式,每次输出文件给程序员的时候,都需要考虑这几个问题:

    1、图片的格式

    • PSD:这是原版的设计图,每张图片都应该至少保存一个PSD文件,每次修改都是修改PSD文件。只有最终输出的时候才考虑其他格式。
    • JPG:是有损压缩,意思是通过损失质量来换取高压缩率。所以,JPG每保存一次,图片质量就下降一次。所以,不要反复保存同一张JPG。不支持透明
    • PNG:现在比较流行的格式,无损压缩,如果颜色数不改变的话,不会牺牲图像质量。支持透明
    • GIF:只支持256色,支持透明,支持动画。

    2、图片的颜色数。是使用24位真彩色还是使用固定颜色数(如256或128或64或32种或2种颜色)

    颜色数越小,图片的文件尺寸越小,图片的质量越低。所以,每次保存的时候,要根据图片的具体情况来决定使用多少种颜色数。 如果是颜色丰富的照片,那可能必须使用真彩色才能保持原始的质量。如果有大量的渐变色,也很难用少量颜色数来表现。如果是颜色较少的图标,那就需要多试验几次,看看最少用几种颜色数就可以表现出设计的原貌。如果是纯黑白图,那用2种颜色就够了。

    如果有透明的话,情况稍复杂。有些透明图只有透明不透明两种状态,那只需要用1种颜色代表透明即可,可以使用256色以下的颜色数。但是,有些图是有半透明的情况存在的,例如圆形的图标,如果边缘是透明的,就必须有半透明的存在才能比较圆滑。如果有半透明,就必须使用真彩色模式,因为只有真彩色模式才可能用很多种不同的颜色代表不同的透明度。

    3、图片的压缩方式

    还有一些设置会影响图片压缩后的质量和大小

    首先,如果不是输出成真彩色,那就需要选择调色板。同样是256色,到底选择哪256种颜色结果是不一样的。例如一张蓝天的图,显然调色板里就没必要有红色。而早期的一些手机和计算机,调色板是固定的,就是说只有固定好的标准256种颜色可以用,这个叫Web Safe调色板。幸运的是,现在这些老设备基本可以不用考虑了。但是Photoshop还是提供了几种不同的调色板,例如“可感知”、“可选择”、“受限”等等,有时候可能需要试试不同的选项,看看哪种调色板对当前的图片最合适。

    其次,图片抖动算法,或者在Photoshop里叫仿色。真彩色的图片,如果用256色来表示,必然有一些颜色会显示不出来,这时候就是用抖动算法来解决的。就是用某一个图案来模仿某种颜色。例如报纸上的照片,用放大镜看都是小黑点,这就是用黑白两色来模仿更丰富的灰度。彩色也是一样,可以用一些图案来模拟颜色的过度。例如下面的图片,第一张是真彩色图片,第二张是不经过抖动,直接转成256色的结果,第三张是经过抖动之后,同样使用256色的结果。经过这样处理,可以使用256色或者更少的颜色来模拟真彩色的效果。

    Dithering_example_undithered.png Dithering_example_undithered_web_palette.png Dithering_example_dithered_web_palette.png

    最后,即使都是PNG,颜色数也一样,也会有不同的文件大小。这是因为图片文件里面可以包含了一些其他信息,例如拍照日期、使用的相机等等。这些信息也会占地方。而对于最终的显示来说,没有什么意义。所以,有一些图片优化工具的存在,就是可以去掉这些不必要的信息,让图片更精简。

    [编辑]具体操作方法

    幸运的是,Adobe显然也意识到上面这些是美工经常要使用的功能,所以在Photoshop 最近的版本中,专门增加了一个菜单选项,整合了上面这些功能。这就是“存储为Web或设备所用格式”,英文版式"Save for web & devices"。这里,设备指的就是手机一类的设备。

    每次需要输出图片的时候,先保存PSD,然后就选择这个菜单。在弹出的窗口里,先选择使用哪种图片格式,例如PNG-8代表PNG格式的256色图片,PNG-24代表PNG格式真彩色图片。GIF格式只支持256色,JPEG格式只支持真彩色,所以就没有其他选择。然后,如果选择了真彩色格式,就没什么太多可以设置的了,直接保存即可。如果选择了PNG-8或者GIF格式,那就可以选择颜色数、调色板和仿色算法。例如下面的图片,只有黑白,就没必要使用真彩色格式,但是如果使用2色也不太好看,因为边缘是有一些灰度的。我经过试验,发现32色跟真彩色的区别几乎肉眼就分辨不出来了,所以最终选择了32色。其他的调色板和仿色没做太多调整,就用的默认选项。因为这个图片不复杂,调整也看不出来。经过这个调整之后,原始的真彩色图片是10.4K大小,而32色的看起来完全一样的优化后的图片,只有1.6K,文件尺寸小了6倍!这只是页面中的一张图,同样的页面下还有另外两张图,原来3张图一共要26.1K,优化之后只有5K!

    而另一个看不到的好处是,这个功能会把图片中一些不必要的信息扔掉,使得尺寸更加精简,同时也避免了一些手机可能没有实现完整的PNG格式解析,导致某些图片显示不正常,例如E72。

    Image:Savetoweb.png

    所以,请大家以后在输出图片的时候,一定都要使用“存储为Web和设备所用格式”这个功能,而不要使用“存储为”这个功能。虽然“存储为”这里也能选择PNG格式,但是这里保存的PNG文件带有很多Photoshop自己的标记和冗余信息,文件过大,同时有些设备可能显示不正常。

    总之,要聪明地选择颜色数和各种压缩选项,让图片既精美又可以迅速下载。

    展开全文
  • 前端优化之图片优化自动化 (2011-10-21 11:10:11) 标签: 分类:未分类 前端图片优化介绍 随着前端页面越来越复杂,尤其是一些社区型的页面中,图片成了页面中不可或缺的资源,并且随着产品功能的叠加图片大小...
    前端优化之图片优化自动化    (2011-10-21 11:10:11)
    
    标签:    分类:未分类

    前端图片优化介绍

    随着前端页面越来越复杂,尤其是一些社区型的页面中,图片成了页面中不可或缺的资源,并且随着产品功能的叠加图片大小越来越多。以下是几个网站的图片所占的比重。

    由于图片是二进制文件,并不能像js、css、html那些源代码文件一样可以通过gzip压缩大大减小文件的大小。所以图片优化主要是选择合适的图片格式,在不降低图片质量的情况下去掉图片里的元数据信息。

    常用的一些优化方案

    目前图片优化使用比较多的主要是下面几种方式:

    1. 选择合适的图片格式,如:png代替gif,尽量使用png8
    2. png使用pngout优化,jpg使用jpgtran
    3. 通过yahoo的smush进行
    4. 通过google的page speed插件进行

    这些优化方案虽然结果都能将图片优化,但需要比较多的人工操作。如使用smush,先要上传文件,优化完了后还要下载文件。在项目时间限制或者改动很频繁的情况下很多时候就把图片优化这一非常重要的优化步骤给忽略了。

    那如何尽量减少人工操作带来的麻烦和不确定因素呢?

    如果在前端模块编译的时候,有图片自动优化的功能,上线前模块编译的时候得到就是优化后的图片,开发人员完全不用管图片优化了,但又不影响线上图片优化的结果。

    如何结合一些工具做到图片优化完全自动化呢?先要考虑目前开发中经常用到的图片格式。

    图片格式

    开发中经常用到的图片格式主要有如下几种:

    1. 不透明的gif,全透明的gif,动画gif
    2. 不透明的png,全透明的png,半透明(alpha透明)的png
    3. jpg图片

    对于全透明的png,ie6要通过下面的filter进行hack。

    对于alpha透明的png,ie6下必须使用png24,目前还没有找到ie6下hack alpha透明png8的方式,如果有哪位大拿对这个有解决方案,麻烦告诉我。

    经过筛选和优化原则要满足上面的图片格式的条件,选择的软件如下:

    1. 动画gif使用gifsicle
    2. png使用pngcrush
    3. jpeg使用jpegtran

    软件安装

    需要安装imagmagick, gifsicle, jpegtran, pngcrush,安装脚本如下:

    01 #!/bin/sh
    02 #安装imagemagick
    03 wget ftp://ftp.kddlabs.co.jp/graphics/ImageMagick/ImageMagick-6.6.4-10.tar.gz
    04 tar zxvf ImageMagick-6.6.4-10.tar.gz
    05 cd ImageMagick-6.6.4-10
    06 ./configure
    07 make
    08 make install
    09 cd ../
    10 #安装gifsicle
    12 tar zxvf gifsicle-1.60.tar.gz
    13 cd gifsicle-1.60
    14 make
    15 make install
    16 cd ../
    17 #安装jpegtran
    19 tar zxvf droppatch.v8.tar.gz
    20 sudo cp ./jpegtran /usr/local/bin
    21 #安装pngcrush
    23 tar zxvf pngcrush-1.7.13.tar.gz
    24 cd pngcrush-1.7.13
    25 sudo make
    26 sudo cp ./pngcrush /usr/local/bin

    将上面的代码拷贝到一个文件如:image.s,执行dos2unix image.sh,然后执行sh image.sh安装软件

    图片优化

    图片优化的代码如下:
    01 #/bin/bash
    02 OPTI_PATH=$1
    03 cd $OPTI_PATH;
    04 CURRENT_PATH=$PWD;
    05 SH_LIST='jpegtran gifsicle pngcrush';
    06 COMMOND_EXIST=1
    07 #先检查相关的软件是否已经正确安装
    08 for ITEM in $SH_LIST
    09 do
    10     SH_EXIST=`which $ITEM 2>/dev/null | wc -l`;
    11     if [[ $SH_EXIST == '0' ]]; then
    12         echo "$ITEM commond not exist";
    13         COMMOND_EXIST=0;
    14     fi
    15 done
    16 if [[ "COMMOND_EXIST" == "0" ]]; then
    17 exit 1;
    18 fi
    19 #优化jpg
    20 JPG_FILES=`find . -type f -name "*.jpg" -or -name "*.jpeg"`;
    21 for FILE in $JPG_FILES
    22 do
    23     OUTPUT_FILE="$FILE.png"
    24     jpegtran -optimize -progressive -copy none -outfile $OUTPUT_FILE $FILE > /dev/null
    25     mv $OUTPUT_FILE $FILE
    26 done
    27 #优化gif
    28 GIF_FILES=`find . -type f -name "*.gif"`;
    29 for FILE in $GIF_FILES
    30 do
    31     DEPTH=`identify $FILE | wc -l`;
    32     if [[ "$DEPTH" == "1" ]]; then
    33         OUTPUT_FILE="$FILE.png";
    34         OUTPUT_FILE_LEN=${#OUTPUT_FILE}-8;
    35         NEW_FILE_SUB=${OUTPUT_FILE:0:$OUTPUT_FILE_LEN};
    36         NEW_FILE="$NEW_FILE_SUB.png"
    37         convert $FILE $NEW_FILE > /dev/null;
    38     else
    39         OUTPUT_FILE="$FILE.gif"
    40         gifsicle -o $OUTPUT_FILE $FILE > /dev/null
    41         mv $OUTPUT_FILE $FILE
    42     fi
    43 done
    44 #优化png
    45 PNG_FILES=`find . -type f -name "*.png"`;
    46 for FILE in $PNG_FILES
    47 do
    48     OUTPUT_FILE="$FILE.png"
    49     pngcrush -rem alla -brute -reduce $FILE $OUTPUT_FILE > /dev/null
    50     mv $OUTPUT_FILE $FILE
    51 done
    52 cd $CURRENT_PATH;
    将上面的代码保存如:image-optimation.sh,执行dosunix image-optizimation.sh,
    然后执行sh image-optizimation.sh imgdir 就可以将imgdir目录下的图片进行优化,上线的时候只要拷贝优化后的图片就可以了。

    优化结果

    以下是前端一个模块的优化前后的文件大小比较:

    优化后,图片大小减小了66.6K,优化率达到35.2%。

    从数据中可以发现,png图片优化还是非常多的,也是优化准则里尽量使用png图片的原因。

    其他优化工具

    除了上面用到的图片优化工具,还有其他很多可以优化图片的工具。但各种各样的小问题,最终并没有使用它们。

    1. pngrewrite
    2. optpng
    3. pngout
    4. pngquant

    展开全文
  • Android性能优化系列之Bitmap图片优化

    万次阅读 2017-04-01 00:26:23
    在Android开发过程中,Bitmap往往会给开发者带来一些困扰,因为对Bitmap操作不慎,就容易造成OOM(Java.lang.OutofMemoryError - 内存溢出),本篇博客,我们将一起探讨Bitmap的性能优化。为什么Bitmap会导致OOM?1.每...

    在Android开发过程中,Bitmap往往会给开发者带来一些困扰,因为对Bitmap操作不慎,就容易造成OOM(Java.lang.OutofMemoryError - 内存溢出),本篇博客,我们将一起探讨Bitmap的性能优化。

    为什么Bitmap会导致OOM?

    1.每个机型在编译ROM时都设置了一个应用堆内存VM值上限dalvik.vm.heapgrowthlimit,用来限定每个应用可用的最大内存,超出这个最大值将会报OOM。这个阀值,一般根据手机屏幕dpi大小递增,dpi越小的手机,每个应用可用最大内存就越低。所以当加载图片的数量很多时,就很容易超过这个阀值,造成OOM。

    2.图片分辨率越高,消耗的内存越大,当加载高分辨率图片的时候,将会非常占用内存,一旦处理不当就会OOM。例如,一张分辨率为:1920x1080的图片。如果Bitmap使用 ARGB_8888 32位来平铺显示的话,占用的内存是1920x1080x4个字节,占用将近8M内存,可想而知,如果不对图片进行处理的话,就会OOM。

    3.在使用ListView, GridView等这些大量加载view的组件时,如果没有合理的处理缓存,大量加载Bitmap的时候,也将容易引发OOM

    Bitmap基础知识

    一张图片Bitmap所占用的内存 = 图片长度 x 图片宽度 x 一个像素点占用的字节数
    而Bitmap.Config,正是指定单位像素占用的字节数的重要参数。
    这里写图片描述

    其中,A代表透明度;R代表红色;G代表绿色;B代表蓝色。

    ALPHA_8
    表示8位Alpha位图,即A=8,一个像素点占用1个字节,它没有颜色,只有透明度
    ARGB_4444
    表示16位ARGB位图,即A=4,R=4,G=4,B=4,一个像素点占4+4+4+4=16位,2个字节
    ARGB_8888
    表示32位ARGB位图,即A=8,R=8,G=8,B=8,一个像素点占8+8+8+8=32位,4个字节
    RGB_565
    表示16位RGB位图,即R=5,G=6,B=5,它没有透明度,一个像素点占5+6+5=16位,2个字节

    一张图片Bitmap所占用的内存 = 图片长度 x 图片宽度 x 一个像素点占用的字节数

    根据以上的算法,可以计算出图片占用的内存,以100*100像素的图片为例

    这里写图片描述

    BitmapFactory解析Bitmap的原理

    BitmapFactory提供的解析Bitmap的静态工厂方法有以下五种:

    Bitmap decodeFile(...)
    Bitmap decodeResource(...)
    Bitmap decodeByteArray(...)
    Bitmap decodeStream(...)
    Bitmap decodeFileDescriptor(...)

    其中常用的三个:decodeFile、decodeResource、decodeStream。
    decodeFile和decodeResource其实最终都是调用decodeStream方法来解析Bitmap

    decodeFile方法代码:

       public static Bitmap decodeFile(String pathName, Options opts) {
            Bitmap bm = null;
            InputStream stream = null;
            try {
                stream = new FileInputStream(pathName);
                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
                    }
                }
            }

    decodeResource方法的代码:

    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;
        }

    decodeStream的逻辑如下:

     public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
            // we don't throw in this case, thus allowing the caller to only check
            // the cache, and not force the image to be decoded.
            if (is == null) {
                return null;
            }
    
            Bitmap bm = null;
    
            Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");
            try {
                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;
        }
    private static Bitmap decodeStreamInternal(InputStream is, Rect outPadding, Options opts) {
            // ASSERT(is != null);
            byte [] tempStorage = null;
            if (opts != null) tempStorage = opts.inTempStorage;
            if (tempStorage == null) tempStorage = new byte[DECODE_BUFFER_SIZE];
            return nativeDecodeStream(is, tempStorage, outPadding, opts);
        }

    从上面的代码可以看出,decodeStream的代码最终会调用以下两个native方法之一

    nativeDecodeAsset()
    nativeDecodeStream()

    这两个native方法只是对应decodeFile和decodeResource、decodeStream来解析的,像decodeByteArray、decodeFileDescriptor也有专门的native方法负责解析Bitmap。

    decodeFile、decodeResource的区别在于他们方法的调用路径不同:

    decodeFile->decodeStream
    decodeResource->decodeResourceStream->decodeStream

    decodeResource在解析时多调用了一个decodeResourceStream方法,而这个decodeResourceStream方法代码如下:

    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);
        }

    其中对Options进行处理了,在得到opts.inDensity属性的前提下,如果我们没有对该属性设定值,那么将opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;赋定这个默认的Density值,这个默认值为160,为标准的dpi比例,即在Density=160的设备上1dp=1px,这个方法中还有这么一行

    opts.inTargetDensity = res.getDisplayMetrics().densityDpi;

    对opts.inTargetDensity进行了赋值,该值为当前设备的densityDpi值,所以说在decodeResourceStream方法中主要做了两件事:

    1.对opts.inDensity赋值,没有则赋默认值160
    2.对opts.inTargetDensity赋值,没有则赋当前设备的densityDpi值

    之后参数将传入decodeStream方法,该方法中在调用native方法进行解析Bitmap后会调用这个方法setDensityFromOptions(bm, opts);:

    private static void setDensityFromOptions(Bitmap outputBitmap, Options opts) {
            if (outputBitmap == null || opts == null) return;
    
            final int density = opts.inDensity;
            if (density != 0) {
                outputBitmap.setDensity(density);
                final int targetDensity = opts.inTargetDensity;
                if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) {
                    return;
                }
    
                byte[] np = outputBitmap.getNinePatchChunk();
                final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np);
                if (opts.inScaled || isNinePatch) {
                    outputBitmap.setDensity(targetDensity);
                }
            } else if (opts.inBitmap != null) {
                // bitmap was reused, ensure density is reset
                outputBitmap.setDensity(Bitmap.getDefaultDensity());
            }
        }

    主要就是把刚刚赋值过的两个属性inDensity和inTargetDensity给Bitmap进行赋值,不过并不是直接赋给Bitmap就完了,中间有个判断,当inDensity的值与inTargetDensity或与设备的屏幕Density不相等时,则将应用inTargetDensity的值,如果相等则应用inDensity的值。

    所以总结来说,setDensityFromOptions方法就是把inTargetDensity的值赋给Bitmap,不过前提是opts.inScaled = true;

    进过上面的分析,结论如下:

    在不配置Options的情况下:
    1.decodeFile、decodeStream在解析时不会对Bitmap进行一系列的屏幕适配,解析出来的将是原始大小的图
    2.decodeResource在解析时会对Bitmap根据当前设备屏幕像素密度densityDpi的值进行缩放适配操作,使得解析出来的Bitmap与当前设备的分辨率匹配,达到一个最佳的显示效果,并且Bitmap的大小将比原始的大

    Bitmap的优化策略

    经过上面的分析,我们可以得出Bitmap优化的思路:
    1、BitmapConfig的配置
    2、使用decodeFile、decodeResource、decodeStream进行解析Bitmap时,配置inDensity和inTargetDensity,两者应该相等,值可以等于屏幕像素密度*0.75f
    3、使用inJustDecodeBounds预判断Bitmap的大小及使用inSampleSize进行压缩
    4、对Density>240的设备进行Bitmap的适配(缩放Density)
    5、2.3版本inNativeAlloc的使用
    6、4.4以下版本inPurgeable、inInputShareable的使用
    7、Bitmap的回收

    所以我们根据以上的思路,我们将Bitmap优化的策略总结为以下3种:
    1.对图片质量进行压缩
    2.对图片尺寸进行压缩
    3.使用libjpeg.so库进行压缩

    对图片质量进行压缩

    
            public static Bitmap compressImage(Bitmap bitmap){  
                ByteArrayOutputStream baos = new ByteArrayOutputStream();  
                //质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中  
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);  
                int options = 100;  
                //循环判断如果压缩后图片是否大于50kb,大于继续压缩  
                while ( baos.toByteArray().length / 1024>50) {  
                    //清空baos  
                    baos.reset();  
                    bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos);  
                    options -= 10;//每次都减少10  
                }  
                //把压缩后的数据baos存放到ByteArrayInputStream中  
                ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());  
                //把ByteArrayInputStream数据生成图片  
                Bitmap newBitmap = BitmapFactory.decodeStream(isBm, null, null);  
                return newBitmap;  
            }  

    对图片尺寸进行压缩

        /**
         * 按图片尺寸压缩 参数是bitmap
         * @param bitmap
         * @param pixelW
         * @param pixelH
         * @return
         */
        public static Bitmap compressImageFromBitmap(Bitmap bitmap, int pixelW, int pixelH) {
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os);
            if( os.toByteArray().length / 1024>512) {//判断如果图片大于0.5M,进行压缩避免在生成图片(BitmapFactory.decodeStream)时溢出
                os.reset();
                bitmap.compress(Bitmap.CompressFormat.JPEG, 50, os);//这里压缩50%,把压缩后的数据存放到baos中
            }
            ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            options.inPreferredConfig = Bitmap.Config.RGB_565;
            BitmapFactory.decodeStream(is, null, options);
            options.inJustDecodeBounds = false;
            options.inSampleSize = computeSampleSize(options , pixelH > pixelW ? pixelW : pixelH ,pixelW * pixelH );
            is = new ByteArrayInputStream(os.toByteArray());
            Bitmap newBitmap = BitmapFactory.decodeStream(is, null, options);
            return newBitmap;
        }
    
    
        /**
         * 动态计算出图片的inSampleSize
         * @param options
         * @param minSideLength
         * @param maxNumOfPixels
         * @return
         */
        public static int computeSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
            int initialSize = computeInitialSampleSize(options, minSideLength, maxNumOfPixels);
            int roundedSize;
            if (initialSize <= 8) {
                roundedSize = 1;
                while (roundedSize < initialSize) {
                    roundedSize <<= 1;
                }
            } else {
                roundedSize = (initialSize + 7) / 8 * 8;
            }
            return roundedSize;
        }
    
        private static int computeInitialSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
            double w = options.outWidth;
            double h = options.outHeight;
            int lowerBound = (maxNumOfPixels == -1) ? 1 : (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
            int upperBound = (minSideLength == -1) ? 128 :(int) Math.min(Math.floor(w / minSideLength), Math.floor(h / minSideLength));
            if (upperBound < lowerBound) {
                return lowerBound;
            }
            if ((maxNumOfPixels == -1) && (minSideLength == -1)) {
                return 1;
            } else if (minSideLength == -1) {
                return lowerBound;
            } else {
                return upperBound;
            }
        }
    

    使用libjpeg.so库进行压缩

    除了通过设置simpleSize根据图片尺寸压缩图片和通过Bitmap.compress方法通过压缩图片质量两种方法外,我们还可以使用libjpeg.so这个库来进行压缩。

    libjpeg是广泛使用的开源JPEG图像库,Android所用的是skia的压缩算法,而Skia对libjpeg进行了的封装。
    libjpeg在压缩图像时,有一个参数叫optimize_coding,关于这个参数,libjpeg.doc有如下解释:

    boolean optimize_coding 
    TRUE causes the compressor to compute optimal Huffman coding tables 
    for the image. This requires an extra pass over the data and 
    therefore costs a good deal of space and time. The default is 
    FALSE, which tells the compressor to use the supplied or default 
    Huffman tables. In most cases optimal tables save only a few percent 
    of file size compared to the default tables. Note that when this is 
    TRUE, you need not supply Huffman tables at all, and any you do 
    supply will be overwritten.

    如果设置optimize_coding为TRUE,将会使得压缩图像过程中基于图像数据计算哈弗曼表,由于这个计算会显著消耗空间和时间,默认值被设置为FALSE。

    谷歌的Skia项目工程师们最终没有设置这个参数,optimize_coding在Skia中默认的等于了FALSE,但是问题就随之出现了,如果我们想在FALSE和TRUE时压缩成相同大小的JPEG 图片,FALSE的品质将大大逊色于TRUE的,尽管谷歌工程师没有将该值设置为true,但是我们可以自己编译libjpeg进行图片的压缩。

    libjpeg的官网下载地址:http://www.ijg.org/
    从官网下载之后,我们必须自己对其进行编译。

    编译libjpeg
    下载最新的源码,解压后将所有文件放到jni目录中,准备用ndk编译
    1、新建config.sh,将ndk中的交叉编译工具加入其中,内容如下:

    NDK=/opt/ndk/android-ndk-r10e/
    PLATFORM=$NDK/platforms/android-9/arch-arm/
    PREBUILT=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86/
    CC=$PREBUILT/bin/arm-linux-androideabi-gcc
    ./configure --prefix=/home/linc/jpeg-9b/jni/dist --host=arm CC="$CC --sysroot=$PLATFORM"

    2、执行此脚本

    $ sh config.sh 
    ...
    checking whether to build shared libraries... no
    checking whether to build static libraries... yes
    ...
    config.status: creating Makefile
    config.status: creating jconfig.h

    首先,它生成了Makefile,我们可以直接使用此Makefile进行编译;其次,它生成了重要的头文件,jconfig.h.
    但是这个Makefile是编译static库而不是共享库的。
    此时,我们可以执行构建命令进行编译:

    jni$ make install-libLTLIBRARIES
    libtool: install: ranlib /home/linc/jpeg-9b/jni/dist/lib/libjpeg.a

    3、Android.mk
    使用ndk-build指令编译,需要手动编写Android.mk文件,内容如下:

    LOCAL_PATH:= $(call my-dir)
    include $(CLEAR_VARS)
    
    LOCAL_ARM_MODE := arm
    
    LOCAL_SRC_FILES :=jaricom.c jcapimin.c jcapistd.c jcarith.c jccoefct.c jccolor.c \
            jcdctmgr.c jchuff.c jcinit.c jcmainct.c jcmarker.c jcmaster.c \
            jcomapi.c jcparam.c jcprepct.c jcsample.c jctrans.c jdapimin.c \
            jdapistd.c jdarith.c jdatadst.c jdatasrc.c jdcoefct.c jdcolor.c \
            jddctmgr.c jdhuff.c jdinput.c jdmainct.c jdmarker.c jdmaster.c \
            jdmerge.c jdpostct.c jdsample.c jdtrans.c jerror.c jfdctflt.c \
            jfdctfst.c jfdctint.c jidctflt.c jidctfst.c jidctint.c jquant1.c \
            jquant2.c jutils.c jmemmgr.c jmemnobs.c
    
    LOCAL_C_INCLUDES := $(LOCAL_PATH)
    LOCAL_CFLAGS += -O3 -fstrict-aliasing -fprefetch-loop-arrays \
        -DANDROID -DANDROID_TILE_BASED_DECODE -DENABLE_ANDROID_NULL_CONVERT
    
    
    LOCAL_MODULE := libjpeg
    
    LOCAL_MODULE_TAGS := optional
    
    # unbundled branch, built against NDK.
    LOCAL_SDK_VERSION := 17
    
    include $(BUILD_SHARED_LIBRARY)

    其中LOCAL_SRC_FILES后面的源文件可以参考刚刚生成的Makefile。
    在jni目录上一级使用ndk-build编译即可。

    $ ndk-build
    [armeabi] Compile arm    : jpeg <= jaricom.c
    ...
    [armeabi] Compile arm    : jpeg <= jmemnobs.c
    [armeabi] SharedLibrary  : libjpeg.so
    [armeabi] Install        : libjpeg.so => libs/armeabi/libjpeg.so

    在Android项目引入编译好的libjpeg
    首先把so库加载到libs中,然后将编译好的头文件拷贝到项目的jni文件夹下,就可以使用Android的具体函数了,具体使用分为如下几步:

    1、将Android的bitmap解码并转换为RGB数据
    2、为JPEG对象分配空间并初始化
    3、指定压缩数据源
    4、获取文件信息
    5、为压缩设定参数,包括图像大小,颜色空间
    6、开始压缩
    7、压缩完毕
    8、释放资源

    #include <string.h>
    #include <android/bitmap.h>
    #include <android/log.h>
    #include <jni.h>
    #include <stdio.h>
    #include <setjmp.h>
    #include <math.h>
    #include <stdint.h>
    #include <time.h>
    #include "jpeglib.h"
    #include "cdjpeg.h"     /* Common decls for cjpeg/djpeg applications */
    #include "jversion.h"       /* for version message */
    #include "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=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) {
        int nComponent = 3;
    
        struct jpeg_compress_struct jcs;
    
        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;
             }
         //为JPEG对象分配空间并初始化
        jpeg_create_compress(&jcs);
        //获取文件信息
        FILE* f = fopen(outfilename, "wb");
        if (f == NULL) {
            return 0;
        }
        //指定压缩数据源
        jpeg_stdio_dest(&jcs, f);
        jcs.image_width = w;
        jcs.image_height = h;
        if (optimize) {
            LOGI("optimize==ture");
        } else {
            LOGI("optimize==false");
        }
    
        jcs.arith_code = false;
        jcs.input_components = nComponent;
        if (nComponent == 1)
            jcs.in_color_space = JCS_GRAYSCALE;
        else
            jcs.in_color_space = JCS_RGB;
    
        jpeg_set_defaults(&jcs);
        jcs.optimize_coding = optimize;
        //为压缩设定参数,包括图像大小,颜色空间
        jpeg_set_quality(&jcs, quality, true);
        //开始压缩
        jpeg_start_compress(&jcs, TRUE);
    
        JSAMPROW row_pointer[1];
        int row_stride;
        row_stride = jcs.image_width * nComponent;
        while (jcs.next_scanline < jcs.image_height) {
            row_pointer[0] = &data[jcs.next_scanline * row_stride];
            //写入数据
            jpeg_write_scanlines(&jcs, row_pointer, 1);
        }
    
        if (jcs.optimize_coding) {
                LOGI("optimize==ture");
            } else {
                LOGI("optimize==false");
            }
        //压缩完毕
        jpeg_finish_compress(&jcs);
        //释放资源
        jpeg_destroy_compress(&jcs);
        fclose(f);
    
        return 1;
    }
    
    typedef struct {
        uint8_t r;
        uint8_t g;
        uint8_t b;
    } rgb;
    
    //将java string转换为char*
    char* jstrinTostring(JNIEnv* env, jbyteArray barr) {
        char* rtn = NULL;
        jsize alen = (*env)->GetArrayLength(env, barr);
        jbyte* ba = (*env)->GetByteArrayElements(env, barr, 0);
        if (alen > 0) {
            rtn = (char*) malloc(alen + 1);
            memcpy(rtn, ba, alen);
            rtn[alen] = 0;
        }
        (*env)->ReleaseByteArrayElements(env, barr, ba, 0);
        return rtn;
    }
    //jni方法入口
    jstring Java_net_bither_util_NativeUtil_compressBitmap(JNIEnv* env,
            jobject thiz, jobject bitmapcolor, int w, int h, int quality,
            jbyteArray fileNameStr, jboolean optimize) {
    
        AndroidBitmapInfo infocolor;
        BYTE* pixelscolor;
        int ret;
        BYTE * data;
        BYTE *tmpdata;
        char * fileName = jstrinTostring(env, fileNameStr);
        //解码Android bitmap信息,并存储值infocolor中
        if ((ret = AndroidBitmap_getInfo(env, bitmapcolor, &infocolor)) < 0) {
            LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
            return (*env)->NewStringUTF(env, "0");;
        }
        if ((ret = AndroidBitmap_lockPixels(env, bitmapcolor, &pixelscolor)) < 0) {
            LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
        }
    
        BYTE r, g, b;
        data = NULL;
        data = malloc(w * h * 3);
        tmpdata = data;
        int j = 0, i = 0;
        int color;
        //将bitmap转换为rgb数据
        for (i = 0; i < h; i++) {
            for (j = 0; j < w; j++) {
                color = *((int *) pixelscolor);
                r = ((color & 0x00FF0000) >> 16);
                g = ((color & 0x0000FF00) >> 8);
                b = color & 0x000000FF;
                *data = b;
                *(data + 1) = g;
                *(data + 2) = r;
                data = data + 3;
                pixelscolor += 4;
    
            }
    
        }
        AndroidBitmap_unlockPixels(env, bitmapcolor);
        //进行压缩
        int resultCode= generateJPEG(tmpdata, w, h, quality, fileName, optimize);
        free(tmpdata);
        if(resultCode==0){
            jstring result=(*env)->NewStringUTF(env, error);
            error=NULL;
            return result;
        }
        return (*env)->NewStringUTF(env, "1"); //success
    }

    新建Android.mk,生成可执行文件:

    LOCAL_PATH:= $(call my-dir)
    include $(CLEAR_VARS)
    
    LOCAL_SRC_FILES:= jpeg_compress.cpp
    
    LOCAL_MODULE:= jtest
    
    LOCAL_LDLIBS :=-llog
    LOCAL_LDLIBS += $(LOCAL_PATH)/libjpeg.so
    LOCAL_C_INCLUDES := $(LOCAL_PATH)
    
    LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
    LOCAL_MODULE_TAGS := debug 
    
    include $(BUILD_EXECUTABLE)

    本篇博客总结了3种图片压缩的方法,大家可以根据自己的情况进行相应的使用,如有错漏,欢迎留言。

    参考文章:
    http://blog.csdn.net/lincyang/article/details/51085737

    展开全文
  • Android内存优化之图片优化

    万次阅读 多人点赞 2019-04-08 11:52:23
    关于图片优化,大概如下 为什么要进行图片优化 相信大概刚开始学习Android的时候有过图片过大而直接报错的情况,下面简单介绍一下OOM问题,Android支持的图片格式及图片优化的几种方式 什么是OOM?:Android系统的...

    关于图片优化,大概如下

    为什么要进行图片优化
    在这里插入图片描述
    相信大概刚开始学习Android的时候有过图片过大而直接报错的情况,下面简单介绍一下OOM问题,Android支持的图片格式及图片优化的几种方式
    在这里插入图片描述
    什么是OOM?:Android系统的进程(APP级别)有最大的内存限制,超过这个限制系统就会抛出)OOM错误
    图片OOM问题产生的几种情况
    1.一个页面一次加载过多的图片
    2.加载大图片没有进行压缩
    3.Android列表加载大量bitmap没有使用缓存(第三方框架)

    Android支持的图片格式
    png:无损压缩的图片格式,支持透明通道,占用的空间一般比较大
    Jpeg:有损压缩的图片格式,不支持透明通道
    webp:由谷歌2010年发布,支持无损与有损,比较理想
    gif:支持多帧动画,但安卓本身图片库不支持,需要用到第三方框架

    为了方便理解,还得介绍一下图片占用内存的知识
    如何计算:图片宽图片高一个像素占的内存大小

    所以由上可见,图片储存优化的方式如下
    1.尺寸优化:通过减小宽高来实现
    2.质量压缩:改变一个像素占用的内存(优化解码率)
    3.内存重用:需要用到inBitmap属性

    先从尺寸压缩开始说起
    主要起作用的为两个方法
    intJustDecodeBounds=true(可以在不加载图片的情况下获得图片的宽高)
    inSampleSize(用合适的压缩比)
    !!!如果只是单纯的改变ImageView的大小,不会对图片产生任何作用(需要对bitmap进行优化
    可能还不是很清楚,所以贴张图
    在这里插入图片描述
    质量压缩
    常见的图片格式在设置在UI上之前需要经过解码过程
    使用RGB-565代替ARGB-8888可以降低图片占用内存,上面那张图已经有了,如下红色矩形类内在这里插入图片描述
    内存重用
    InBItmap,后面的图需<=第一张图的大小,下图为第二张图片重用第一张图
    mCurrentBitmap为第一张图的Bitmap
    在这里插入图片描述
    然后小提一下Bitmap的内存管理
    在3.0前,对于像素数据的支持保存在本地内存中,
    在3.0后,像素数据和位图都储存在Dalvik堆中

    以上为图片的储存优化,接下来介绍图片加载优化
    首先要了解两个资源文件夹
    mipmap和drawable,一般情况下,启动图标放在mipmap文件夹,
    然后这两个文件夹的区别为setHasMipmap的值
    mipmap为true,drawable为false

    如何让Android图片资源适配各种分辨率的手机
    先了解分辨率和DPI ,分辨率单位为px
    1.240-320 xhdpi
    2.320-480 xxhdpi !!!主流
    3.480-640 xxxhdpi

    下面提供两种方案
    方案一:为每种dpi都出一套图片资源 (为设计师增加了工作量,且增大了APK大小)
    方案二:提供一套需要支持的最大dpi的图片,(自动渲染的概念);

    下面介绍一下图片匹配的规则(假设我的手机为480dpi分辨率的手机,我将需要的图片放在xhdpi文件夹下,系统处理如下)
    1.先查找xxhdpi文件夹,没有找到,往下走
    2.再查找xxxhdpi文件夹,没有找到,往下走
    3.再查找nohdpi文件夹,如果还没有找到,才会去xhdpi文件夹

    其中有一个问题,一个手机从不同的文件夹下拿图片,显示效果是不同的,如果不是匹配的文件夹,系统会对其图片放大或缩小

    常见的图片加载优化方法
    1.异步优化:图片放在后台请求(不占用主UI的资源)
    2.图片缓存:对于列表中的图片进行缓存(本地文件中的缓存)
    3.网络请求:使用OkHttp进行图片请求(优点很多)
    4.懒加载:当图片呈现到可视区域再进行加载

    其中图片的加载一般用多级缓存加载流程
    在这里插入图片描述
    主要原因如下
    如果每次都用网络请求(服务器受不了,且浪费用户流量),需通常使用内存加本地文件两级缓存(如何单纯使用本地文件,不安全,容易被清除掉)。

    小提一下超大图片加载方案
    使用图片压缩来加载超大图片,会看不清图片细节
    使用BitmapRegionDecoder来解决

    下面为大家介绍一下几大图片加载的框架
    1.Universal ImageLoader,优点如下
    多线程,支持下载监听
    bitmap裁剪
    ListView暂停加载
    自由配置
    较好的控制图片的加载过程
    提供在较慢的网络下对图片进行加载

    2.Picasso,特点如下
    缓存图片原图到本地
    使用ARGB-8888(占用内存比较大)

    3.Glide(来自谷歌),特点如下
    与Activity/Fragment生命周期一致
    改变图片的大小再加载到内存

    4.Fresco(来自脸谱),特点如下(重点介绍)
    性能好:首次加载图片速度非常快,用户体验好
    内存表现出色:有效的对内存块的图片进行了管理(共享内存机制来解决图片加载的oom问题)
    渐进式预览:大致展示图片轮廓,然后逐渐展示清晰图片
    多图请求 封装了先加载底分辨率图片,然后再显示高分辨率图片
    图片呈现效果:自定义占位符,圆角图
    Gif,Webp格式
    在5.0以下系统,Fresco将图片放到一个特别的内存区域。当然,在图片不显示的时候,占用的内存会自动被释放。这会使得APP更加流畅,减少因图片内存占用而引发的OOM。
    加载Gif图和WebP动图在任何一个Android开发者眼里看来都是一件非常头疼的事情。每一帧都是一张很大的Bitmap,每一个动画都有很多帧。Fresco让你没有这些烦恼,它处理好每一帧并管理好你的内存。

    以上就是大概的内容了,由于博主时间问题,会于以后写关于框架的使用及关于图片优化的小案例

    展开全文
  • 网站图片优化

    千次阅读 2015-04-10 15:06:39
    第一、网站图片要分类别放置。 第二、图片要注意是不是都加了一些搜索引擎能够识别的标签“alt属性”这点对图片网站来说是一“棵救命稻草”可不要放弃哦! 第三、图片如果能够在网页上加上一些说明最好不过,这样...
  • 网页图片优化

    2010-04-07 14:06:00
    做一个网络设计师,那么图形优化会是一门基本功,进行网络图形设计的最终目标就是,设计制作出下载十分迅速的优美...优化图片的三种方式比较:一 画质上看 只要选对格式,视觉效果上看不出来差异,二 体积上看 20K的jpg
  • Android 图片优化

    2015-11-26 17:29:04
    本地图片操作时的优化车商的有一个发车传照的功能,需要上传本地的照片到服务器,上传之前需要对照片做是否横向拍摄的拍摄的检测,以前一直写法(见以下代码),只要使用了这句代码BitmapFactory.decodeFile(file...
  • 注重图片优化

    2013-10-10 11:19:07
    对于大多数网站而言,并没有刻意注重图片优化,在图片优化上并没有下过多的工夫,然而以图片为主题展现信息的网站却是成千上万,不在少数。并且能够通过图片信息获取大量流量,这使我们不得不引起关注以及重视。对于...
  • web图片优化

    千次阅读 2010-05-31 16:28:00
    使用YSlow 的Smush.it 工具,对web站点的图片进行优化
  • 游戏UI动态加载图片优化

    千次阅读 2018-08-09 18:59:48
    UI优化主要是针对图集,还有一些依赖项的优化,针对的是内存优化,上面这些都是关于静态UI的优化,这个是作为程序员都要经历的阶段。其实很多开发者对使用UGUI或者NGUI已经墨守成规了,我们如果真想去优化,其实完全...
  • 图片格式和应用场景介绍 1. JPEG (Joint Photographic Experts Group) 联合图像专家小组针对彩色照片而广泛使用的一种有损压缩图形格式。 介绍: 栅格图形。常用文件扩展名为.jpg, 也有.jpeg, .jpe。 JPEG在互联网上...
  • Android性能优化之图片优化先总结下为什么要做图片优化,以及图片优化的好处吧!先总结下图片存储优化一、什么是OOM?二、图片OOM产生的情况图片存储优化主要是以下三点:尺寸压缩、质量压缩、内存重用一、尺寸压缩...
  • 图片优化: 1.尽量减少图片的颜色、色深,这样也可以减少图片的大小,比如色深减少到原来的一半,图片容量就可以减少到原来的三分之一。在准备资源图片的时候,和设计师商量 ,如果在界面效果可以接受的情况...
  • 很多的前端开发者都比较关心性能的优化的问题,今天主要讲一下图片优化的见解和总结,可能很多人大神都知道我列出的知识点,那么开始整理一下图片优化的笔记吧,可能废话有点多,语言组织不到位的地方,大家多担待 ...
  • HTTP Archieve有个统计,... PageSpeed或者Yahoo的14条性能优化规则无不把图片优化作为重要的优化手段,本文覆盖了Web图片优化的方方面面,从基本的图片格式选择、到尚未被广泛支持的响应式图片均有所提及。 Google
  • 图片本身进行操作。尽量不要使用setImageBitmap、setImageResource、BitmapFactory.decodeResource来设置一张大图,因为这些方法在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存对...

空空如也

1 2 3 4 5 ... 20
收藏数 21,919
精华内容 8,767
关键字:

图片优化