Android图片压缩、Drawable和Bitmap转换、bitmap和base64转换

1. Android图片压缩、Drawable和Bitmap转换、bitmap和base64转换

1.1. Drawable和Bitmap之间的转化

1.1.1. bitmap和Drawable间的区别

  Bitmap - 称作位图,一般位图的文件格式后缀为bmp,当然编码器也有很多如RGB565、RGB888。作为一种逐像素的显示对象执行效率高,但是缺点也很明显存储效率低。我们理解为一种存储对象比较好。
Drawable - 作为Android平下通用的图形对象,它可以装载常用格式的图像,比如GIF、PNG、JPG,当然也支持BMP,当然还提供一些高级的可视化对象,比如渐变、图形等。
  两者间的简单对比:

对比项BitmipDrawable
显示清晰度相同相同
电内存占比
支持压缩
支持色相色差调整
支持旋转
支持透明色
绘图速度
支持像素操作

  Drawable在内存占用和绘制速度这两个非常关键的点上胜过Bitmap
  我们经常会遇到需要在代码中获取资源文件下图片的问题

Bitmap    bmp = BitmapFactory.decodeResource(getResources(),R.drawable.ic );

或者

Drawable drawable = getResources().getDrawable(R.drawable.ic);

或者

iv.setImageResource(R.drawable.ic_launcher);

  获取路径下的图片资源:

String fileName = "/data/data/com.test/aa.png; 
Bitmap bm = BitmapFactory.decodeFile(fileName); 
iv.setImageBitmap(bm); //占用内存

1.1.2. Drawable转换成Bitmap

第一种:

BitmapDrawable bd = (BitmapDrawable) drawable;
Bitmap bm= bd.getBitmap();

第二种:

public static Bitmap drawableToBitmap(Drawable drawable) {   
        // 取 drawable 的长宽   
        int w = drawable.getIntrinsicWidth();   
        int h = drawable.getIntrinsicHeight();   
   
        // 取 drawable 的颜色格式   
        Bitmap.Config config = drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888   
                : Bitmap.Config.RGB_565;   
        // 建立对应 bitmap   
        Bitmap bitmap = Bitmap.createBitmap(w, h, config);   
        // 建立对应 bitmap 的画布   
        Canvas canvas = new Canvas(bitmap);   
        drawable.setBounds(0, 0, w, h);   
        // 把 drawable 内容画到画布中   
        drawable.draw(canvas);   
        return bitmap;   
    }   

1.1.3. Bitmap转换成Drawable

第一种:

Drawable drawable = new BitmapDrawable(bitmap); 

第二种:

BitmapDrawable bd= new BitmapDrawable(getResource(), bm);    

BitmapDrawable是Drawable的子类,可以直接使用。

1.2. Drawable和Bitmap之间的转化

1.2.1. bitmap和base64之间的转换

public class BitmapUtil {
    /**
     * bitmap转为base64
     * @param bitmap
     * @return
     */
    public static String bitmapToBase64(Bitmap bitmap) {

        String result = null;
        ByteArrayOutputStream baos = null;
        try {
            if (bitmap != null) {
                baos = new ByteArrayOutputStream();
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);

                baos.flush();
                baos.close();

                byte[] bitmapBytes = baos.toByteArray();
                result = Base64.encodeToString(bitmapBytes, Base64.DEFAULT);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (baos != null) {
                    baos.flush();
                    baos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    /**
     * base64转为bitmap
     * @param base64Data
     * @return
     */
    public static Bitmap base64ToBitmap(String base64Data) {
        byte[] bytes = Base64.decode(base64Data, Base64.DEFAULT);
        return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
    }
}

1.2.2. 注意事项

  当我们通过站长工具进行图片和Base64的转换时,会有一段前缀信息data:image/jpeg;base64,
  如果Base64中带有这样的前缀信息,我们需要将其去除之后,才能调用上面的Bitmap.base64ToBitmap()方法,将其转换为Bitmap。
在这里插入图片描述

1.2.3. bitmap转换工具

 
package ***.***.***.***;
 
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.Base64;
import android.view.View;
import android.view.View.MeasureSpec;
import android.widget.ImageView;
import com.***.***.R;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
 
/**
 * 描述:图片处理类.
 *
 */
public class AbImageUtil {
 
    /**
     * The tag.
     */
    private static String TAG = "AbImageUtil";
 
 
 
    private static OnImageUtilsListener onImageUtilsListener;
 
    public void setOnImageUtilsListener(OnImageUtilsListener listener) {
        onImageUtilsListener = listener;
    }
 
    public interface OnImageUtilsListener {
        void backBitmap(Bitmap bt);
    }
 
    /**
     * 描述:缩放图片.压缩
     *
     * @param file      File对象
     * @param newWidth  新图片的宽
     * @param newHeight 新图片的高
     * @return Bitmap 新图片
     */
    @SuppressWarnings("resource")
    public static Bitmap scaleImg(File file, int newWidth, int newHeight) {
        Bitmap resizeBmp = null;
        try {
            BitmapFactory.Options opts = new BitmapFactory.Options();
            // 设置为true,decodeFile先不创建内存 只获取一些解码边界信息即图片大小信息
            opts.inJustDecodeBounds = true;
            // BitmapFactory.decodeFile(file.getPath(), opts);
            BitmapFactory.decodeFileDescriptor(new FileInputStream(file.getPath()).getFD(), null, opts);
            if (newWidth != -1 && newHeight != -1) {
                // inSampleSize=2表示图片宽高都为原来的二分之一,即图片为原来的四分之一
                // 缩放可以将像素点打薄
                int srcWidth = opts.outWidth; // 获取图片的原始宽度
                int srcHeight = opts.outHeight;// 获取图片原始高度
                int destWidth = 0;
                int destHeight = 0;
                // 缩放的比例
                double ratio = 0.0;
                if (srcWidth < newWidth || srcHeight < newHeight) {
                    ratio = 0.0;
                    destWidth = srcWidth;
                    destHeight = srcHeight;
                    // 按比例计算缩放后的图片大小
                } else if (srcWidth > srcHeight) {
                    ratio = (double) srcWidth / newWidth;
                    destWidth = newWidth;
                    destHeight = (int) (srcHeight / ratio);
                } else {
                    ratio = (double) srcHeight / newHeight;
                    destHeight = newHeight;
                    destWidth = (int) (srcWidth / ratio);
                }
                // 缩放的比例,缩放是很难按准备的比例进行缩放的,目前我只发现只能通过inSampleSize来进行缩放,其值表明缩放的倍数,SDK中建议其值是2的指数值
                opts.inSampleSize = (int) ratio + 1;
                // 设置大小
                opts.outHeight = destHeight;
                opts.outWidth = destWidth;
            } else {
                opts.inSampleSize = 1;
            }
            // 创建内存
            opts.inJustDecodeBounds = false;
            // 使图片不抖动
            opts.inDither = false;
            resizeBmp = BitmapFactory.decodeFileDescriptor(new FileInputStream(file.getPath()).getFD(), null, opts);
            // resizeBmp = BitmapFactory.decodeFile(file.getPath(), opts);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return resizeBmp;
    }
 
    /**
     * 描述:缩放图片,不压缩的缩放.
     *
     * @param bitmap    the bitmap
     * @param newWidth  新图片的宽
     * @param newHeight 新图片的高
     * @return Bitmap 新图片
     */
    public static Bitmap scaleImg(Bitmap bitmap, int newWidth, int newHeight) {
        if (bitmap == null) {
            return null;
        }
        if (newHeight <= 0 || newWidth <= 0) {
            return bitmap;
        }
        // 获得图片的宽高
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
 
        if (width <= 0 || height <= 0) {
            return null;
        }
        // 计算缩放比例
        float scaleWidth = ((float) newWidth) / width;
        float scaleHeight = ((float) newHeight) / height;
        // 取得想要缩放的matrix参数
        Matrix matrix = new Matrix();
        matrix.postScale(scaleWidth, scaleHeight);
        // 得到新的图片
        Bitmap newBm = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
        return newBm;
    }
 
    /**
     * 描述:缩放图片.
     *
     * @param bitmap the bitmap
     * @param scale  比例
     * @return Bitmap 新图片
     */
    public static Bitmap scaleImg(Bitmap bitmap, float scale) {
        Bitmap resizeBmp = null;
        try {
            // 获取Bitmap资源的宽和高
            int bmpW = bitmap.getWidth();
            int bmpH = bitmap.getHeight();
            // 注意这个Matirx是android.graphics底下的那个
            Matrix mt = new Matrix();
            // 设置缩放系数,分别为原来的0.8和0.8
            mt.postScale(scale, scale);
            resizeBmp = Bitmap.createBitmap(bitmap, 0, 0, bmpW, bmpH, mt, true);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return resizeBmp;
    }
 
    /**
     * 描述:裁剪图片.
     *
     * @param file      File对象
     * @param newWidth  新图片的宽
     * @param newHeight 新图片的高
     * @return Bitmap 新图片
     */
    public static Bitmap cutImg(File file, int newWidth, int newHeight) {
        Bitmap newBitmap = null;
        try {
            BitmapFactory.Options opts = new BitmapFactory.Options();
            // 设置为true,decodeFile先不创建内存 只获取一些解码边界信息即图片大小信息
            opts.inJustDecodeBounds = true;
            BitmapFactory.decodeFileDescriptor(new FileInputStream(file.getPath()).getFD(), null, opts);
            // BitmapFactory.decodeFile(file.getPath(), opts);
            if (newWidth != -1 && newHeight != -1) {
                // inSampleSize=2表示图片宽高都为原来的二分之一,即图片为原来的四分之一
                // 缩放可以将像素点打薄,裁剪前将图片缩放一些
                int srcWidth = opts.outWidth; // 获取图片的原始宽度
                int srcHeight = opts.outHeight;// 获取图片原始高度
                int destWidth = 0;
                int destHeight = 0;
                int cutSrcWidth = newWidth * 2;
                int cutSrcHeight = newHeight * 2;
 
                // 缩放的比例
                double ratio = 0.0;
                if (srcWidth < cutSrcWidth || srcHeight < cutSrcHeight) {
                    ratio = 0.0;
                    destWidth = srcWidth;
                    destHeight = srcHeight;
                    // 按比例计算缩放后的图片大小
                } else if (srcWidth > srcHeight) {
                    ratio = (double) srcWidth / cutSrcWidth;
                    destWidth = cutSrcWidth;
                    destHeight = (int) (srcHeight / ratio);
                } else {
                    ratio = (double) srcHeight / cutSrcHeight;
                    destHeight = cutSrcHeight;
                    destWidth = (int) (srcWidth / ratio);
                }
                // 缩放的比例,缩放是很难按准备的比例进行缩放的,目前我只发现只能通过inSampleSize来进行缩放,其值表明缩放的倍数,SDK中建议其值是2的指数值
                opts.inSampleSize = (int) ratio + 1;
                // 设置大小
                opts.outHeight = destHeight;
                opts.outWidth = destWidth;
            } else {
                opts.inSampleSize = 1;
            }
            // 创建内存
            opts.inJustDecodeBounds = false;
            // 使图片不抖动
            opts.inDither = false;
            // Bitmap resizeBmp = BitmapFactory.decodeFile(file.getPath(), opts);
            Bitmap resizeBmp = BitmapFactory.decodeFileDescriptor(new FileInputStream(file.getPath()).getFD(), null, opts);
            if (resizeBmp != null) {
                newBitmap = cutImg(resizeBmp, newWidth, newHeight);
            }
            if (newBitmap != null) {
                return newBitmap;
            } else {
                return resizeBmp;
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return newBitmap;
        } catch (IOException e) {
            e.printStackTrace();
            return newBitmap;
        }
    }
 
    /**
     * 描述:裁剪图片.
     *
     * @param bitmap    the bitmap
     * @param newWidth  新图片的宽
     * @param newHeight 新图片的高
     * @return Bitmap 新图片
     */
    public static Bitmap cutImg(Bitmap bitmap, int newWidth, int newHeight) {
        if (bitmap == null) {
            return null;
        }
        Bitmap newBitmap = null;
        if (newHeight <= 0 || newWidth <= 0) {
            return bitmap;
        }
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
 
        if (width <= 0 || height <= 0) {
            return null;
        }
        int offsetX = 0;
        int offsetY = 0;
 
        if (width > newWidth) {
            offsetX = (width - newWidth) / 2;
        }
        if (height > newHeight) {
            offsetY = (height - newHeight) / 2;
        }
 
        newBitmap = Bitmap.createBitmap(bitmap, offsetX, offsetY, newWidth, newHeight);
        return newBitmap;
    }
 
    /**
     * Drawable转Bitmap.
     *
     * @param drawable 要转化的Drawable
     * @return Bitmap
     */
    public static Bitmap drawableToBitmap(Drawable drawable) {
        Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), drawable.getOpacity() != PixelFormat.OPAQUE ? Config.ARGB_8888 : Config.RGB_565);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
        drawable.draw(canvas);
        return bitmap;
    }
 
    /**
     * Drawable对象转换Bitmap对象.
     *
     * @param bitmap 要转化的Bitmap对象
     * @return Drawable 转化完成的Drawable对象
     */
    @SuppressWarnings("deprecation")
    public static Drawable bitmapToDrawable(Bitmap bitmap) {
        BitmapDrawable mBitmapDrawable = null;
        try {
            if (bitmap == null) {
                return null;
            }
            mBitmapDrawable = new BitmapDrawable(bitmap);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return mBitmapDrawable;
    }
 
    /**
     * 将Bitmap转换为byte[].
     *
     * @param bitmap          the bitmap
     * @param mCompressFormat 图片格式 Bitmap.CompressFormat.JPEG,CompressFormat.PNG
     * @param needRecycle     是否需要回收
     * @return byte[] 图片的byte[]
     */
    public static byte[] bitmap2Bytes(Bitmap bitmap, Bitmap.CompressFormat mCompressFormat, final boolean needRecycle) {
        byte[] result = null;
        ByteArrayOutputStream output = null;
        try {
            output = new ByteArrayOutputStream();
            bitmap.compress(mCompressFormat, 100, output);
            result = output.toByteArray();
            if (needRecycle) {
                bitmap.recycle();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (output != null) {
                try {
                    output.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return result;
    }
 
    /**
     * 描述:将byte[]转换为Bitmap.
     *
     * @param b 图片格式的byte[]数组
     * @return bitmap 得到的Bitmap
     */
    public static Bitmap bytes2Bimap(byte[] b) {
        Bitmap bitmap = null;
        try {
            if (b.length != 0) {
                bitmap = BitmapFactory.decodeByteArray(b, 0, b.length);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bitmap;
    }
 
    /**
     * 将ImageView转换为Bitmap.
     *
     * @param view 要转换为bitmap的View
     * @return byte[] 图片的byte[]
     */
    public static Bitmap imageView2Bitmap(ImageView view) {
        Bitmap bitmap = null;
        try {
            bitmap = Bitmap.createBitmap(view.getDrawingCache());
            view.setDrawingCacheEnabled(false);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bitmap;
    }
 
    /**
     * 将View转换为Drawable.需要最上层布局为Linearlayout
     *
     * @param view 要转换为Drawable的View
     * @return BitmapDrawable Drawable
     */
    @SuppressWarnings("deprecation")
    public static Drawable view2Drawable(View view) {
        BitmapDrawable mBitmapDrawable = null;
        try {
            Bitmap newbmp = view2Bitmap(view);
            if (newbmp != null) {
                mBitmapDrawable = new BitmapDrawable(newbmp);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return mBitmapDrawable;
    }
 
    /**
     * 将View转换为Bitmap.需要最上层布局为Linearlayout
     *
     * @param view 要转换为bitmap的View
     * @return byte[] 图片的byte[]
     */
    public static Bitmap view2Bitmap(View view) {
        Bitmap bitmap = null;
        try {
            if (view != null) {
                view.setDrawingCacheEnabled(true);
                view.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
                view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
                view.buildDrawingCache();
                bitmap = view.getDrawingCache();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bitmap;
    }
 
    /**
     * 将View转换为byte[].
     *
     * @param view           要转换为byte[]的View
     * @param compressFormat the compress format
     * @return byte[] View图片的byte[]
     */
    public static byte[] view2Bytes(View view, Bitmap.CompressFormat compressFormat) {
        byte[] b = null;
        try {
            Bitmap bitmap = AbImageUtil.view2Bitmap(view);
            b = AbImageUtil.bitmap2Bytes(bitmap, compressFormat, true);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return b;
    }
 
    /**
     * 描述:旋转Bitmap为一定的角度.
     *
     * @param bitmap  the bitmap
     * @param degrees the degrees
     * @return the bitmap
     */
    public static Bitmap rotateBitmap(Bitmap bitmap, float degrees) {
        Bitmap mBitmap = null;
        try {
            Matrix m = new Matrix();
            m.setRotate(degrees % 360);
            mBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m, false);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return mBitmap;
    }
 
    /**
     * 描述:旋转Bitmap为一定的角度并四周暗化处理.
     *
     * @param bitmap  the bitmap
     * @param degrees the degrees
     * @return the bitmap
     */
    public static Bitmap rotateBitmapTranslate(Bitmap bitmap, float degrees) {
        Bitmap mBitmap = null;
        int width;
        int height;
        try {
            Matrix matrix = new Matrix();
            if ((degrees / 90) % 2 != 0) {
                width = bitmap.getWidth();
                height = bitmap.getHeight();
            } else {
                width = bitmap.getHeight();
                height = bitmap.getWidth();
            }
            int cx = width / 2;
            int cy = height / 2;
            matrix.preTranslate(-cx, -cy);
            matrix.postRotate(degrees);
            matrix.postTranslate(cx, cy);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return mBitmap;
    }
 
    /**
     * 转换图片转换成圆形.
     *
     * @param bitmap 传入Bitmap对象
     * @return the bitmap
     */
    public static Bitmap toRoundBitmap(Bitmap bitmap) {
        if (bitmap == null) {
            return null;
        }
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        float roundPx;
        float left, top, right, bottom, dst_left, dst_top, dst_right, dst_bottom;
        if (width <= height) {
            roundPx = width / 2;
            top = 0;
            bottom = width;
            left = 0;
            right = width;
            height = width;
            dst_left = 0;
            dst_top = 0;
            dst_right = width;
            dst_bottom = width;
        } else {
            roundPx = height / 2;
            float clip = (width - height) / 2;
            left = clip;
            right = width - clip;
            top = 0;
            bottom = height;
            width = height;
            dst_left = 0;
            dst_top = 0;
            dst_right = height;
            dst_bottom = height;
        }
 
        Bitmap output = Bitmap.createBitmap(width, height, Config.ARGB_8888);
        Canvas canvas = new Canvas(output);
        final int color = 0xff424242;
        final Paint paint = new Paint();
        final Rect src = new Rect((int) left, (int) top, (int) right, (int) bottom);
        final Rect dst = new Rect((int) dst_left, (int) dst_top, (int) dst_right, (int) dst_bottom);
        final RectF rectF = new RectF(dst);
        paint.setAntiAlias(true);
        canvas.drawARGB(0, 0, 0, 0);
        paint.setColor(color);
        canvas.drawRoundRect(rectF, roundPx, roundPx, paint);
        paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
        canvas.drawBitmap(bitmap, src, dst, paint);
        return output;
    }
 
 
 
    public static byte[] imagePathToByte(String path) {
        byte[] data = null;
        FileInputStream input = null;
        try {
            input = new FileInputStream(new File(path));
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            byte[] buf = new byte[1024];
            int numBytesRead = 0;
            while ((numBytesRead = input.read(buf)) != -1) {
                output.write(buf, 0, numBytesRead);
            }
            data = output.toByteArray();
            output.close();
            input.close();
        } catch (FileNotFoundException ex1) {
            ex1.printStackTrace();
        } catch (IOException ex1) {
            ex1.printStackTrace();
        }
        return data;
    }
 
 
 
    /**
     * @param bitmap     原图
     * @param edgeLength 希望得到的正方形部分的边长
     * @return 缩放截取正中部分后的位图。
     */
    public static Bitmap centerSquareScaleBitmap(Bitmap bitmap, int edgeLength) {
        if (null == bitmap || edgeLength <= 0) {
            return null;
        }
 
        Bitmap result = bitmap;
        int widthOrg = bitmap.getWidth();
        int heightOrg = bitmap.getHeight();
 
        if (widthOrg > edgeLength && heightOrg > edgeLength) {
            // 压缩到一个最小长度是edgeLength的bitmap
            int longerEdge = edgeLength * Math.max(widthOrg, heightOrg) / Math.min(widthOrg, heightOrg);
            int scaledWidth = widthOrg > heightOrg ? longerEdge : edgeLength;
            int scaledHeight = widthOrg > heightOrg ? edgeLength : longerEdge;
            Bitmap scaledBitmap;
 
            try {
                scaledBitmap = Bitmap.createScaledBitmap(bitmap, scaledWidth, scaledHeight, true);
            } catch (Exception e) {
                return null;
            }
 
            // 从图中截取正中间的正方形部分。
            int xTopLeft = (scaledWidth - edgeLength) / 2;
            int yTopLeft = (scaledHeight - edgeLength) / 2;
 
            try {
                result = Bitmap.createBitmap(scaledBitmap, xTopLeft, yTopLeft, edgeLength, edgeLength);
                scaledBitmap.recycle();
            } catch (Exception e) {
                return null;
            }
        }
        return result;
    }
 
    /**
     * @param bitmap     原图
     * @param edgeLength 希望得到的正方形部分的边长
     * @return 缩放截取上面部分后的位图。
     */
    public static Bitmap topSquareScaleBitmap(Bitmap bitmap, int edgeLength) {
        if (null == bitmap || edgeLength <= 0) {
            return null;
        }
 
        Bitmap result = bitmap;
        int widthOrg = bitmap.getWidth();
        int heightOrg = bitmap.getHeight();
 
        if (widthOrg > edgeLength && heightOrg > edgeLength) {
            // 压缩到一个最小长度是edgeLength的bitmap
            int longerEdge = edgeLength * Math.max(widthOrg, heightOrg) / Math.min(widthOrg, heightOrg);
            int scaledWidth = widthOrg > heightOrg ? longerEdge : edgeLength;
            int scaledHeight = widthOrg > heightOrg ? edgeLength : longerEdge;
            Bitmap scaledBitmap;
 
            try {
                scaledBitmap = Bitmap.createScaledBitmap(bitmap, scaledWidth, scaledHeight, true);
            } catch (Exception e) {
                return null;
            }
 
            // 从图中截取正中间的正方形部分。
            int xTopLeft = (scaledWidth - edgeLength) / 2;
            // int yTopLeft = (scaledHeight - edgeLength) / 2;
            int yTopLeft = 0;
 
            try {
                result = Bitmap.createBitmap(scaledBitmap, xTopLeft, yTopLeft, edgeLength, edgeLength);
                scaledBitmap.recycle();
            } catch (Exception e) {
                return null;
            }
        }
        return result;
    }
 
    /**
     * 转换Resources图片转换成Bitmap.
     */
    public static Bitmap ResourcesToBitmap(Resources resId) {
        Bitmap bmp = BitmapFactory.decodeResource(resId, R.mipmap.ic_launcher);
        return bmp;
    }
    /**
     * bitmap转为base64
     * @param bitmap
     * @return
     */
    public static String bitmapToBase64(Bitmap bitmap) {
 
        String result = null;
        ByteArrayOutputStream baos = null;
        try {
            if (bitmap != null) {
                baos = new ByteArrayOutputStream();
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
 
                baos.flush();
                baos.close();
 
                byte[] bitmapBytes = baos.toByteArray();
                result ="data:image/jpg;base64," + Base64.encodeToString(bitmapBytes, Base64.DEFAULT);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (baos != null) {
                    baos.flush();
                    baos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result;
    }
 
    /**
     * base64转为bitmap
     * @param base64Data
     * @return
     */
    public static Bitmap base64ToBitmap(String base64Data) {
        byte[] bytes = Base64.decode(base64Data, Base64.DEFAULT);
        return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
    }
    /**
     * bitmap转成string
     *
     * @param bitmap
     * @return
     */
    public static String bitmapToString(Bitmap bitmap)
    {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();// outputstream
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
        byte[] appicon = baos.toByteArray();// 转为byte数组
        return Base64.encodeToString(appicon, Base64.DEFAULT);
 
    }
 
    /**
     * string转成bitmap
     *
     * @param st
     */
    public static Bitmap stringToBitmap(String st)
    {
        Bitmap bitmap = null;
        try
        {
            byte[] bitmapArray;
            bitmapArray = Base64.decode(st, Base64.DEFAULT);
            bitmap =
                    BitmapFactory.decodeByteArray(bitmapArray, 0,
                            bitmapArray.length);
            return bitmap;
        }
        catch (Exception e)
        {
            return null;
        }
    }
}

1.3. 图片压缩(质量压缩和尺寸压缩)&Bitmap转成字符串上传

  图片压缩的方法大致上可以认为有两类压缩:质量压缩(不改变图片的尺寸)和尺寸压缩(相当于是像素上的压缩);质量压缩一般可用于上传大图前的处理,这样就可以节省一定的流量,毕竟现在的手机拍照都能达到3M左右了,尺寸压缩一般可用于生成缩略图。

1.3.1. android图片压缩总结

  总结来看,图片有三种存在形式:硬盘上时是file,网络传输时是stream,内存中是stream或bitmap,所谓的质量压缩,它其实只能实现对file的影响,你可以把一个file转成bitmap再转成file,或者直接将一个bitmap转成file时,这个最终的file是被压缩过的,但是中间的bitmap并没有被压缩(或者说几乎没有被压缩,我不确定),因为bigmap在内存中的大小是按像素计算的,也就是width * height,对于质量压缩,并不会改变图片的像素,所以就算质量被压缩了,但是bitmap在内存的占有率还是没变小,但你做成file时,它确实变小了;
  而尺寸压缩由于是减小了图片的像素,所以它直接对bitmap产生了影响,当然最终的file也是相对的变小了;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
 
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
 
/**
 * Image compress factory class
 * 
 * @author 
 *
 */
public class ImageFactory {
 
	/**
	 * Get bitmap from specified image path
	 * 
	 * @param imgPath
	 * @return
	 */
	public Bitmap getBitmap(String imgPath) {
		// Get bitmap through image path
		BitmapFactory.Options newOpts = new BitmapFactory.Options();
		newOpts.inJustDecodeBounds = false;
		newOpts.inPurgeable = true;
		newOpts.inInputShareable = true;
		// Do not compress
		newOpts.inSampleSize = 1;
		newOpts.inPreferredConfig = Config.RGB_565;
		return BitmapFactory.decodeFile(imgPath, newOpts);
	}
	
	/**
	 * Store bitmap into specified image path
	 * 
	 * @param bitmap
	 * @param outPath
	 * @throws FileNotFoundException 
	 */
	public void storeImage(Bitmap bitmap, String outPath) throws FileNotFoundException {
		FileOutputStream os = new FileOutputStream(outPath);
		bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os);
	}
	
	/**
	 * Compress image by pixel, this will modify image width/height. 
	 * Used to get thumbnail
	 * 
	 * @param imgPath image path
	 * @param pixelW target pixel of width
	 * @param pixelH target pixel of height
	 * @return
	 */
	public Bitmap ratio(String imgPath, float pixelW, float pixelH) {
		BitmapFactory.Options newOpts = new BitmapFactory.Options();  
        // 开始读入图片,此时把options.inJustDecodeBounds 设回true,即只读边不读内容
        newOpts.inJustDecodeBounds = true;
        newOpts.inPreferredConfig = Config.RGB_565;
        // Get bitmap info, but notice that bitmap is null now  
        Bitmap bitmap = BitmapFactory.decodeFile(imgPath,newOpts);
          
        newOpts.inJustDecodeBounds = false;  
        int w = newOpts.outWidth;  
        int h = newOpts.outHeight;  
        // 想要缩放的目标尺寸
        float hh = pixelH;// 设置高度为240f时,可以明显看到图片缩小了
	    float ww = pixelW;// 设置宽度为120f,可以明显看到图片缩小了
        // 缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可  
        int be = 1;//be=1表示不缩放  
        if (w > h && w > ww) {//如果宽度大的话根据宽度固定大小缩放  
            be = (int) (newOpts.outWidth / ww);  
        } else if (w < h && h > hh) {//如果高度高的话根据宽度固定大小缩放  
            be = (int) (newOpts.outHeight / hh);  
        }  
        if (be <= 0) be = 1;  
        newOpts.inSampleSize = be;//设置缩放比例
        // 开始压缩图片,注意此时已经把options.inJustDecodeBounds 设回false了
        bitmap = BitmapFactory.decodeFile(imgPath, newOpts);
        // 压缩好比例大小后再进行质量压缩
//        return compress(bitmap, maxSize); // 这里再进行质量压缩的意义不大,反而耗资源,删除
        return bitmap;
	}
	
	/**
	 * Compress image by size, this will modify image width/height. 
	 * Used to get thumbnail
	 * 
	 * @param image
	 * @param pixelW target pixel of width
	 * @param pixelH target pixel of height
	 * @return
	 */
	public Bitmap ratio(Bitmap image, float pixelW, float pixelH) {
		ByteArrayOutputStream os = new ByteArrayOutputStream();
	    image.compress(Bitmap.CompressFormat.JPEG, 100, os);
	    if( os.toByteArray().length / 1024>1024) {//判断如果图片大于1M,进行压缩避免在生成图片(BitmapFactory.decodeStream)时溢出    
	        os.reset();//重置baos即清空baos  
	        image.compress(Bitmap.CompressFormat.JPEG, 50, os);//这里压缩50%,把压缩后的数据存放到baos中  
	    }  
	    ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());  
	    BitmapFactory.Options newOpts = new BitmapFactory.Options();  
	    //开始读入图片,此时把options.inJustDecodeBounds 设回true了  
	    newOpts.inJustDecodeBounds = true;
	    newOpts.inPreferredConfig = Config.RGB_565;
	    Bitmap bitmap = BitmapFactory.decodeStream(is, null, newOpts);  
	    newOpts.inJustDecodeBounds = false;  
	    int w = newOpts.outWidth;  
	    int h = newOpts.outHeight;  
	    float hh = pixelH;// 设置高度为240f时,可以明显看到图片缩小了
	    float ww = pixelW;// 设置宽度为120f,可以明显看到图片缩小了
	    //缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可  
	    int be = 1;//be=1表示不缩放  
	    if (w > h && w > ww) {//如果宽度大的话根据宽度固定大小缩放  
	        be = (int) (newOpts.outWidth / ww);  
	    } else if (w < h && h > hh) {//如果高度高的话根据宽度固定大小缩放  
	        be = (int) (newOpts.outHeight / hh);  
	    }  
	    if (be <= 0) be = 1;  
	    newOpts.inSampleSize = be;//设置缩放比例  
	    //重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了  
	    is = new ByteArrayInputStream(os.toByteArray());  
	    bitmap = BitmapFactory.decodeStream(is, null, newOpts);
	    //压缩好比例大小后再进行质量压缩
//	    return compress(bitmap, maxSize); // 这里再进行质量压缩的意义不大,反而耗资源,删除
	    return bitmap;
	}
	
	/**
	 * Compress by quality,  and generate image to the path specified
	 * 
	 * @param image
	 * @param outPath
	 * @param maxSize target will be compressed to be smaller than this size.(kb)
	 * @throws IOException 
	 */
	public void compressAndGenImage(Bitmap image, String outPath, int maxSize) throws IOException {
		ByteArrayOutputStream os = new ByteArrayOutputStream();
		// scale
		int options = 100;
		// Store the bitmap into output stream(no compress)
        image.compress(Bitmap.CompressFormat.JPEG, options, os);  
        // Compress by loop
        while ( os.toByteArray().length / 1024 > maxSize) {
            // Clean up os
        	os.reset();
        	// interval 10
            options -= 10;
            image.compress(Bitmap.CompressFormat.JPEG, options, os);
        }
        
        // Generate compressed image file
        FileOutputStream fos = new FileOutputStream(outPath);  
        fos.write(os.toByteArray());  
        fos.flush();  
        fos.close();  
	}
	
	/**
	 * Compress by quality,  and generate image to the path specified
	 * 
	 * @param imgPath
	 * @param outPath
	 * @param maxSize target will be compressed to be smaller than this size.(kb)
	 * @param needsDelete Whether delete original file after compress
	 * @throws IOException 
	 */
	public void compressAndGenImage(String imgPath, String outPath, int maxSize, boolean needsDelete) throws IOException {
		compressAndGenImage(getBitmap(imgPath), outPath, maxSize);
		
		// Delete original file
		if (needsDelete) {
			File file = new File (imgPath);
			if (file.exists()) {
				file.delete();
			}
		}
	}
	
	/**
	 * Ratio and generate thumb to the path specified
	 * 
	 * @param image
	 * @param outPath
	 * @param pixelW target pixel of width
	 * @param pixelH target pixel of height
	 * @throws FileNotFoundException
	 */
	public void ratioAndGenThumb(Bitmap image, String outPath, float pixelW, float pixelH) throws FileNotFoundException {
		Bitmap bitmap = ratio(image, pixelW, pixelH);
		storeImage( bitmap, outPath);
	}
	
	/**
	 * Ratio and generate thumb to the path specified
	 * 
	 * @param image
	 * @param outPath
	 * @param pixelW target pixel of width
	 * @param pixelH target pixel of height
	 * @param needsDelete Whether delete original file after compress
	 * @throws FileNotFoundException
	 */
	public void ratioAndGenThumb(String imgPath, String outPath, float pixelW, float pixelH, boolean needsDelete) throws FileNotFoundException {
		Bitmap bitmap = ratio(imgPath, pixelW, pixelH);
		storeImage( bitmap, outPath);
		
		// Delete original file
				if (needsDelete) {
					File file = new File (imgPath);
					if (file.exists()) {
						file.delete();
					}
				}
	}
	
}

1.3.2. 图片质量压缩

    /**
     * 质量压缩方法
     *
     * @param image
     * @return
     */
    public static Bitmap compressImage(Bitmap image) {
 
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        image.compress(Bitmap.CompressFormat.JPEG, 100, baos);// 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
        int options = 90;
 
        while (baos.toByteArray().length / 1024 > 100) { // 循环判断如果压缩后图片是否大于100kb,大于继续压缩
            baos.reset(); // 重置baos即清空baos
            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;
    }

1.3.3. 按比例大小压缩 (路径获取图片)

    /**
     * 图片按比例大小压缩方法
     *
     * @param srcPath (根据路径获取图片并压缩)
     * @return
     */
    public static Bitmap getimage(String srcPath) {
 
        BitmapFactory.Options newOpts = new BitmapFactory.Options();
        // 开始读入图片,此时把options.inJustDecodeBounds 设回true了
        newOpts.inJustDecodeBounds = true;
        Bitmap bitmap = BitmapFactory.decodeFile(srcPath, newOpts);// 此时返回bm为空
 
        newOpts.inJustDecodeBounds = false;
        int w = newOpts.outWidth;
        int h = newOpts.outHeight;
        // 现在主流手机比较多是800*480分辨率,所以高和宽我们设置为
        float hh = 800f;// 这里设置高度为800f
        float ww = 480f;// 这里设置宽度为480f
        // 缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
        int be = 1;// be=1表示不缩放
        if (w > h && w > ww) {// 如果宽度大的话根据宽度固定大小缩放
            be = (int) (newOpts.outWidth / ww);
        } else if (w < h && h > hh) {// 如果高度高的话根据宽度固定大小缩放
            be = (int) (newOpts.outHeight / hh);
        }
        if (be <= 0)
            be = 1;
        newOpts.inSampleSize = be;// 设置缩放比例
        // 重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了
        bitmap = BitmapFactory.decodeFile(srcPath, newOpts);
        return compressImage(bitmap);// 压缩好比例大小后再进行质量压缩
    }

1.3.4. 按比例大小压缩 (Bitmap)

    /**
     * 图片按比例大小压缩方法
     *
     * @param image (根据Bitmap图片压缩)
     * @return
     */
    public static Bitmap compressScale(Bitmap image) {
 
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        image.compress(Bitmap.CompressFormat.JPEG, 100, baos);
 
        // 判断如果图片大于1M,进行压缩避免在生成图片(BitmapFactory.decodeStream)时溢出
        if (baos.toByteArray().length / 1024 > 1024) {
            baos.reset();// 重置baos即清空baos
            image.compress(Bitmap.CompressFormat.JPEG, 80, baos);// 这里压缩50%,把压缩后的数据存放到baos中
        }
        ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
        BitmapFactory.Options newOpts = new BitmapFactory.Options();
        // 开始读入图片,此时把options.inJustDecodeBounds 设回true了
        newOpts.inJustDecodeBounds = true;
        Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, newOpts);
        newOpts.inJustDecodeBounds = false;
        int w = newOpts.outWidth;
        int h = newOpts.outHeight;
        Log.i(TAG, w + "---------------" + h);
        // 现在主流手机比较多是800*480分辨率,所以高和宽我们设置为
        // float hh = 800f;// 这里设置高度为800f
        // float ww = 480f;// 这里设置宽度为480f
        float hh = 512f;
        float ww = 512f;
        // 缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
        int be = 1;// be=1表示不缩放
        if (w > h && w > ww) {// 如果宽度大的话根据宽度固定大小缩放
            be = (int) (newOpts.outWidth / ww);
        } else if (w < h && h > hh) { // 如果高度高的话根据高度固定大小缩放
            be = (int) (newOpts.outHeight / hh);
        }
        if (be <= 0)
            be = 1;
        newOpts.inSampleSize = be; // 设置缩放比例
        // newOpts.inPreferredConfig = Config.RGB_565;//降低图片从ARGB888到RGB565
 
        // 重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了
        isBm = new ByteArrayInputStream(baos.toByteArray());
        bitmap = BitmapFactory.decodeStream(isBm, null, newOpts);
 
        return compressImage(bitmap);// 压缩好比例大小后再进行质量压缩
 
        //return bitmap;
    }

1.4. 按照图片尺寸压缩

public static void compressPicture(String srcPath, String desPath) {
		FileOutputStream fos = null;
		BitmapFactory.Options op = new BitmapFactory.Options();
 
		// 开始读入图片,此时把options.inJustDecodeBounds 设回true了
		op.inJustDecodeBounds = true;
		Bitmap bitmap = BitmapFactory.decodeFile(srcPath, op);
		op.inJustDecodeBounds = false;
 
		// 缩放图片的尺寸
		float w = op.outWidth;
		float h = op.outHeight;
		float hh = 1024f;//
		float ww = 1024f;//
		// 最长宽度或高度1024
		float be = 1.0f;
		if (w > h && w > ww) {
			be = (float) (w / ww);
		} else if (w < h && h > hh) {
			be = (float) (h / hh);
		}
		if (be <= 0) {
			be = 1.0f;
		}
		op.inSampleSize = (int) be;// 设置缩放比例,这个数字越大,图片大小越小.
		// 重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了
		bitmap = BitmapFactory.decodeFile(srcPath, op);
		int desWidth = (int) (w / be);
		int desHeight = (int) (h / be);
		bitmap = Bitmap.createScaledBitmap(bitmap, desWidth, desHeight, true);
		try {
			fos = new FileOutputStream(desPath);
			if (bitmap != null) {
				bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}

需要注意两个问题:
(1)调用getDrawingCache()前先要测量,否则的话得到的bitmap为null,这个我在OnCreate()、OnStart()、OnResume()方法里都试验过。
(2)当调用bitmap.compress(CompressFormat.JPEG, 100, fos);保存为图片时发现图片背景为黑色,如下图:
在这里插入图片描述
这时只需要改成用png保存就可以了,bitmap.compress(CompressFormat.PNG, 100, fos);,如下图:
在这里插入图片描述

在实际开发中,有时候我们需求将文件转换为字符串,然后作为参数进行上传。

1.4.1. 必备工具类图片bitmap转成字符串string与String字符串转换为bitmap图片格式

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Base64;
 
import java.io.ByteArrayOutputStream;
 
/**
 * 
 * 
 * 功能描述:Android开发之常用必备工具类图片bitmap转成字符串string与String字符串转换为bitmap图片格式
 */
public class BitmapAndStringUtils {
    /**
     * 图片转成string
     *
     * @param bitmap
     * @return
     */
    public static String convertIconToString(Bitmap bitmap)
    {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();// outputstream
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
        byte[] appicon = baos.toByteArray();// 转为byte数组
        return Base64.encodeToString(appicon, Base64.DEFAULT);
 
    }
 
    /**
     * string转成bitmap
     *
     * @param st
     */
    public static Bitmap convertStringToIcon(String st)
    {
        // OutputStream out;
        Bitmap bitmap = null;
        try
        {
            // out = new FileOutputStream("/sdcard/aa.jpg");
            byte[] bitmapArray;
            bitmapArray = Base64.decode(st, Base64.DEFAULT);
            bitmap =
                    BitmapFactory.decodeByteArray(bitmapArray, 0,
                            bitmapArray.length);
            // bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
            return bitmap;
        }
        catch (Exception e)
        {
            return null;
        }
    }
}

  如果你的图片是File文件,可以用下面代码:

    /**
     * 图片文件转换为指定编码的字符串
     *
     * @param imgFile  图片文件
     */
    public static String file2String(File imgFile) {
        InputStream in = null;
        byte[] data = null;
        //读取图片字节数组
        try{
            in = new FileInputStream(imgFile);
            data = new byte[in.available()];
            in.read(data);
            in.close();
        } catch (IOException e){
            e.printStackTrace();
        }
        //对字节数组Base64编码
        BASE64Encoder encoder = new BASE64Encoder();
        String result = encoder.encode(data);
        return result;//返回Base64编码过的字节数组字符串
    }
  • 17
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Android 中进行图片压缩可以使用以下两种方式: 1. 使用 BitmapFactory 进行图片压缩 ```java public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // 首先将 inJustDecodeBounds 设置为 true,解析图片的宽高信息,不将图片的像素加载到内存中 final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); // 根据计算出的 inSampleSize 来进行图片的压缩 options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // 将 inJustDecodeBounds 设置为 false,重新解析图片,这次会将图片的像素加载到内存中 options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); } public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { // 获取图片的原始宽高 final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; // 如果图片的宽高比目标宽高大,则进行压缩 if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; // 计算 inSampleSize 的值,确保宽高都不会超过目标宽高 while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { inSampleSize *= 2; } } return inSampleSize; } ``` 调用该方法时,可以指定需要压缩到的目标宽高: ```java Bitmap compressedBitmap = decodeSampledBitmapFromResource(getResources(), R.drawable.image, 500, 500); ``` 2. 使用第三方库进行图片压缩Android 中有很多第三方库可以用来进行图片压缩,比如: - Luban:https://github.com/Curzibn/Luban - Compressor:https://github.com/zetbaitsu/Compressor 这些库通常都提供了简单易用的 API,可以很方便地进行图片压缩

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值