# 文件与Bitmap间的方法


1. 从文件载入Bitmap

/**
 * @brief 从文件载入Bitmap
 * @param path 图片路径
 * @param opts 选项
 * @return Bitmap
 */
public Bitmap loadFromFile(String path, Options opts) {
    try {
        File f = new File(path);
        if (!f.exists() || f.isDirectory()) {
            return null;
        }
        Bitmap bm = BitmapFactory.decodeFile(path, opts);
        return bm;
    } catch (Exception e) {
        return null;
    }
}
/**
 * @brief 从文件载入Bitmap
 * @param path 图片路径
 * @return Bitmap
 */
public Bitmap loadFromFile(String path) {
    return loadFromFile(path, null);
}


2. 载入取样的Bitmap


原宽度和高度的各1/sampleSize大小。


显示图片文件时一般都是取样图,否则很容易outofmemory。


/**
 * @brief 从文件载入采样后的Bitmap
 * @see android.graphics.BitmapFactory.Options#inSampleSize
 */
public Bitmap loadSampleSize(String path, int sampleSize) {
    Options opts = new Options();
    opts.inSampleSize = sampleSize;
    return loadFromFile(path, opts);
}


3. 载入Bitmap边框


其返回Bitmap为null,但Options.outxxx会被填充值。包括outHeight, outWidth, outMimeType。


只读取其高宽信息的话,就不需要读取全部Bitmap了。可结合上个方法,获取倍数缩小的样图。


/**
 * @brief 从文件载入只获边框的Bitmap
 * @see android.graphics.BitmapFactory.Options#inJustDecodeBounds
 */
public Options loadJustDecodeBounds(String path) {
    Options opts = new Options();
    opts.inJustDecodeBounds = true;
    loadFromFile(path, opts);
    return opts;
}


4. 保存Bitmap至文件


/**
 * @brief 保存Bitmap至文件
 * @param bm Bitmap
 * @param path 图片路径
 * @return 成功与否
 */
public boolean compressBitmap(Bitmap bm, String path) {
    FileOutputStream out = null;
    try {
        out = new FileOutputStream(path);
        bm.compress(Bitmap.CompressFormat.JPEG, 100, out);
        out.flush();
    } catch (Exception e) {
        return false;
    } finally {
        try {
            if (out != null) {
                out.close();
            }
        } catch (IOException e) {
        }
    }
    return true;
}


5. 读取图片方向信息


Bitmap图片的方法==!!!


/**
 * @brief 读取图片方向信息
 * @param path 图片路径
 * @return 角度
 */
public int readPhotoDegree(String path) {
    int degree = 0;
    try {
        ExifInterface exifInterface = new ExifInterface(path);
        int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,
                ExifInterface.ORIENTATION_NORMAL);
        switch (orientation) {
        case ExifInterface.ORIENTATION_ROTATE_90:
            degree = 90;
            break;
        case ExifInterface.ORIENTATION_ROTATE_180:
            degree = 180;
            break;
        case ExifInterface.ORIENTATION_ROTATE_270:
            degree = 270;
            break;
        default:
            degree = 0;
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    return degree;
}


# 处理Bitmap的方法


1. 生成缩略图


public Bitmap extractThumbnail(Bitmap src, int width, int height) {
    return ThumbnailUtils.extractThumbnail(src, width, height,
            ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
}


2. 缩放


/**
 * @brief 缩放Bitmap,自动回收原Bitmap
 * @see ImageUtil#scaleBitmap(Bitmap, int, int, boolean)
 */
public Bitmap scaleBitmap(Bitmap src, int dstWidth, int dstHeight) {
    return scaleBitmap(src, dstWidth, dstHeight, true);
}
/**
 * @brief 缩放Bitmap
 * @param src 源Bitmap
 * @param dstWidth 目标宽度
 * @param dstHeight 目标高度
 * @param isRecycle 是否回收原图像
 * @return Bitmap
 */
public Bitmap scaleBitmap(Bitmap src, int dstWidth, int dstHeight, boolean isRecycle) {
    if (src.getWidth() == dstWidth && src.getHeight() == dstHeight) {
        return src;
    }
    Bitmap dst = Bitmap.createScaledBitmap(src, dstWidth, dstHeight, false);
    if (isRecycle && dst != src) {
        src.recycle();
    }
    return dst;
}


3. 裁剪


/**
 * @brief 裁剪Bitmap,自动回收原Bitmap
 * @see ImageUtil#cropBitmap(Bitmap, int, int, int, int, boolean)
 */
public Bitmap cropBitmap(Bitmap src, int x, int y, int width, int height) {
    return cropBitmap(src, x, y, width, height, true);
}
/**
 * @brief 裁剪Bitmap
 * @param src 源Bitmap
 * @param x 开始x坐标
 * @param y 开始y坐标
 * @param width 截取宽度
 * @param height 截取高度
 * @param isRecycle 是否回收原图像
 * @return Bitmap
 */
public Bitmap cropBitmap(Bitmap src, int x, int y, int width, int height, boolean isRecycle) {
    if (x == 0 && y == 0 && width == src.getWidth() && height == src.getHeight()) {
        return src;
    }
    Bitmap dst = Bitmap.createBitmap(src, x, y, width, height);
    if (isRecycle && dst != src) {
        src.recycle();
    }
    return dst;
}


4. 旋转


/**
 * @brief 旋转Bitmap,自动回收原Bitmap
 * @see ImageUtil#rotateBitmap(Bitmap, int, boolean)
 */
public Bitmap rotateBitmap(Bitmap src, int degree) {
    return rotateBitmap(src, degree, true);
}
/**
 * @brief 旋转Bitmap,顺时针
 * @param src 源Bitmap
 * @param degree 旋转角度
 * @param isRecycle 是否回收原图像
 * @return Bitmap
 */
public Bitmap rotateBitmap(Bitmap src, int degree, boolean isRecycle) {
    if (degree % 360 == 0) {
        return src;
    }
    int w = src.getWidth();
    int h = src.getHeight();
    Matrix matrix = new Matrix();
    matrix.postRotate(degree);
    Bitmap dst = Bitmap.createBitmap(src, 0, 0, w, h, matrix, true);
    if (isRecycle && dst != src) {
        src.recycle();
    }
    return dst;
}


# OpenCV处理Bitmap的方法


除了导入OpenCV的jar包,我们只需要libopencv_java.so,就可以下做进行操作了。


1. 常规性的init


static {
    if (!OpenCVLoader.initDebug()) {
        Log.e(TAG, "OpenCVLoader initDebug failed.");
    }
}


2. 常规性处理流程


bmp -> mat -> 接口处理 -> mat_new -> bmp_new。


private interface IHandler {
    Mat proc(Mat mat_bmp);
}
private Bitmap handle(Bitmap src, IHandler handler) {
    Mat mat_bmp = new Mat(src.getHeight(), src.getWidth(), CvType.CV_8UC4);
    Utils.bitmapToMat(src, mat_bmp, false); // Bitmap->Mat
    Mat mat_new = handler.proc(mat_bmp); // handle mat
    Bitmap bmp_new = Bitmap.createBitmap(mat_new.cols(), mat_new.rows(),
            Bitmap.Config.ARGB_8888);
    Utils.matToBitmap(mat_new, bmp_new, false); // Mat->Bitmap
    src.recycle();
    return bmp_new;
}


3. 缩放


/**
 * @brief 缩放Bitmap
 * @param src 源Bitmap
 * @param dstWidth 目标宽度
 * @param dstHeight 目标高度
 * @return Bitmap
 */
public Bitmap scaleBitmap2(Bitmap src, final int dstWidth, final int dstHeight) {
    if (src.getWidth() == dstWidth && src.getHeight() == dstHeight) {
        return src;
    }
    return handle(src, new IHandler() {
        @Override
        public Mat proc(Mat mat_bmp) {
            Mat mat_new = new Mat();
            Imgproc.resize(mat_bmp, mat_new, new Size(dstWidth, dstHeight));
            return mat_new;
        }
    });
}


4. 裁剪


/**
 * @brief 裁剪Bitmap
 * @param src 源Bitmap
 * @param x 开始x坐标
 * @param y 开始y坐标
 * @param width 截取宽度
 * @param height 截取高度
 * @return Bitmap
 */
public Bitmap cropBitmap2(Bitmap src, final int x, final int y, final int width,
        final int height) {
    if (x == 0 && y == 0 && width == src.getWidth() && height == src.getHeight()) {
        return src;
    }
    if (x + width > src.getWidth()) {
        throw new IllegalArgumentException("x + width must be <= bitmap.width()");
    }
    if (y + height > src.getHeight()) {
        throw new IllegalArgumentException("y + height must be <= bitmap.height()");
    }
    return handle(src, new IHandler() {
        @Override
        public Mat proc(Mat mat_bmp) {
            Rect roi = new Rect(x, y, width, height);
            Mat mat_new = new Mat(mat_bmp, roi);
            return mat_new;
        }
    });
}


5. 旋转


/**
 * @brief 旋转Bitmap,逆时针
 * @param src 源Bitmap
 * @param degree 旋转角度
 * @return Bitmap
 * @see <a href="http://stackoverflow.com/questions/12852578/p_w_picpath-rotation-with-opencv-in-android-cuts-off-the-edges-of-an-p_w_picpath">More</a>
 */
public Bitmap rotateBitmap2(Bitmap src, final int degree) {
    if (degree % 360 == 0) {
        return src;
    }
    return handle(src, new IHandler() {
        @Override
        public Mat proc(Mat mat_bmp) {
            // 计算旋转后图像的宽高
            double radians = Math.toRadians(degree);
            double sin = Math.abs(Math.sin(radians));
            double cos = Math.abs(Math.cos(radians));
            int width = mat_bmp.width();
            int height = mat_bmp.height();
            int newWidth = (int) (width * cos + height * sin);
            int newHeight = (int) (width * sin + height * cos);
            // 能把原图像和旋转后图像同时放入的外框
            int frameWidth = Math.max(width, newWidth);
            int frameHeight = Math.max(height, newHeight);
            Size frameSize = new Size(frameWidth, frameHeight);
            Mat mat_frame = new Mat(frameSize, mat_bmp.type());
            // 将原图像copy进外框
            int offsetX = (frameWidth - width) / 2;
            int offsetY = (frameHeight - height) / 2;
            Mat mat_frame_submat = mat_frame.submat(offsetY, offsetY + height, offsetX, offsetX
                    + width);
            mat_bmp.copyTo(mat_frame_submat);
            // 旋转外框
            Point center = new Point(frameWidth / 2, frameHeight / 2);
            Mat mat_rot = Imgproc.getRotationMatrix2D(center, degree, 1.0);
            Mat mat_res = new Mat(); // result
            Imgproc.warpAffine(mat_frame, mat_res, mat_rot, frameSize, Imgproc.INTER_LINEAR,
                    Imgproc.BORDER_CONSTANT, Scalar.all(0));
            // 从旋转后的外框获取新图像
            offsetX = (frameWidth - newWidth) / 2;
            offsetY = (frameHeight - newHeight) / 2;
            Mat mat_res_submat = mat_res.submat(offsetY, offsetY + newHeight, offsetX, offsetX
                    + newWidth);
            return mat_res_submat;
        }
    });
}


6. Bitmap效果器


/**
 * @brief Bitmap效果器
 * @author join
 */
public static class Effector {
    private Config config;
    private Mat mat;
    private boolean isGray;
    private Mat mSepiaKernel;
    /**
     * @brief 构造函数
     * @param bmp 源Bitmap
     * @param config 'ARGB_8888' or 'RGB_565'
     */
    public Effector(Bitmap bmp, Config config) {
        Mat mat_bmp = new Mat(bmp.getHeight(), bmp.getWidth(), CvType.CV_8UC4);
        Utils.bitmapToMat(bmp, mat_bmp, false); // Bitmap->Mat
        this.mat = mat_bmp;
        this.config = config;
        this.isGray = false;
    }
    /**
     * @brief 构造函数,config默认为RGB_565
     * @see #BitmapUtil(Bitmap, Config)
     */
    public Effector(Bitmap bmp) {
        this(bmp, Bitmap.Config.RGB_565);
    }
    /**
     * @brief 创建Bitmap
     * @return Bitmap
     */
    public Bitmap create() {
        Mat mat_new = this.mat;
        if (isGray) {
            Mat mat_gray = new Mat(mat_new.rows(), mat_new.cols(), CvType.CV_8UC4);
            Imgproc.cvtColor(mat_new, mat_gray, Imgproc.COLOR_GRAY2BGRA, 4); // 转为灰度4通道Mat
            mat_new = mat_gray;
        }
        Bitmap bmp_new = Bitmap.createBitmap(mat_new.cols(), mat_new.rows(), this.config);
        Utils.matToBitmap(mat_new, bmp_new, false); // Mat->Bitmap
        return bmp_new;
    }
    /**
     * @brief 灰度化Bitmap
     */
    public Effector gray() {
        Mat mat_bmp = this.mat;
        Mat mat_gray = new Mat();
        Imgproc.cvtColor(mat_bmp, mat_gray, Imgproc.COLOR_BGRA2GRAY, 1); // 转为灰度单通道Mat
        this.mat = mat_gray;
        this.isGray = true;
        return this;
    }
    /**
     * @brief Bitmap二值化
     * @pre 需先灰度化{@link #gray()}
     * @param thresh 阈值。type为THRESH_OTSU时无用,其自适应区域阈值。
     * @param maxval 最大值。type为THRESH_BINARY或THRESH_BINARY_INV时才使用。
     * @param type 运算类型
     * @see Imgproc#threshold(Mat, Mat, double, double, int)
     * @see THRESH_OTSU: {@link Imgproc#adaptiveThreshold(Mat, Mat, double, int, int, int, double)}
     */
    public Effector threshold(double thresh, double maxval, int type) {
        if (!isGray) {
            // throw new IllegalArgumentException("must call gray() before this.");
            gray();
        }
        Mat mat_gray = this.mat;
        Imgproc.threshold(mat_gray, mat_gray, thresh, maxval, type);
        return this;
    }
    /**
     * @brief Bitmap二值化
     * @details thresh: 127; maxval: 255; type: THRESH_OTSU;
     * @see #threshold(double, double, int)
     */
    public Effector threshold() {
        return threshold(127, 255, Imgproc.THRESH_OTSU);
    }
    /**
     * @brief Canny算子边缘检测
     * @param threshold1 控制边缘连接的下限阈值
     * @param threshold2 控制强边缘的初始分割的上阈限值
     *  如果一个像素的梯度大于上阈限值,则被认为是边缘像素,如果小于下限阈值,则被抛弃。
     *  如果该点的梯度在两者之间则当这个点与高于上阈限值的像素点连接时我们才保留,否则抛弃。
     */
    public Effector canny(final double threshold1, final double threshold2) {
        Mat mat = this.mat;
        Imgproc.Canny(mat, mat, threshold1, threshold2, 3, false); // Canny边缘检测
        return this;
    }
    /**
     * @brief Canny边缘检测,返回为RGB_565
     * @details threshold1: 80; threshold2: 90;
     * @see #canny(Bitmap, Config)
     */
    public Effector canny() {
        return canny(80, 90);
    }
    /**
     * @brief Sobel处理
     */
    public Effector sobel() {
        Mat mat = this.mat;
        Imgproc.Sobel(mat, mat, CvType.CV_8U, 1, 1); // 一阶差分
        Core.convertScaleAbs(mat, mat, 10, 0); // 线性变换
        return this;
    }
    /**
     * @brief 棕褐色
     */
    public Effector sepia() {
        Mat mat = this.mat;
        Core.transform(mat, mat, getSepiaKernel());
        return this;
    }
    private Mat getSepiaKernel() {
        if (mSepiaKernel != null) {
            return mSepiaKernel;
        }
        mSepiaKernel = new Mat(4, 4, CvType.CV_32F);
        mSepiaKernel.put(0, 0, /* R */0.189f, 0.769f, 0.393f, 0f);
        mSepiaKernel.put(1, 0, /* G */0.168f, 0.686f, 0.349f, 0f);
        mSepiaKernel.put(2, 0, /* B */0.131f, 0.534f, 0.272f, 0f);
        mSepiaKernel.put(3, 0, /* A */0.000f, 0.000f, 0.000f, 1f);
        return mSepiaKernel;
    }
}


# Over


常用的一些方法就这样了^^。