最近遇到一个需求,要把在C中通过opencv渲染的图像,通过jni传到java,再由java层创建BufferedImage并展示。
流程如下:
1. 通过java读取图片为BufferedImage
// 这里加入了使用exif信息修正图片位置的代码
private static BufferedImage readImg(String path) {
File img = new File(path);
if (!img.exists()) {
return null;
}
Metadata metadata = null;
BufferedImage read = null;
try {
read = ImageIO.read(img);
} catch (IOException e) {
e.printStackTrace();
}
try {
if (read == null) {
return null;
}
metadata = ImageMetadataReader.readMetadata(img);
ExifIFD0Directory directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
int width = read.getWidth();
int height = read.getHeight();
if (directory != null) {
int orientation = 1;
try {
orientation = directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
AffineTransform affineTransform = new AffineTransform();
switch (orientation) {
case 1:
break;
case 2: // Flip X
affineTransform.scale(-1.0, 1.0);
affineTransform.translate(-width, 0);
break;
case 3: // PI rotation
affineTransform.translate(width, height);
affineTransform.rotate(Math.PI);
break;
case 4: // Flip Y
affineTransform.scale(1.0, -1.0);
affineTransform.translate(0, -height);
break;
case 5: // - PI/2 and Flip X
affineTransform.rotate(-Math.PI / 2);
affineTransform.scale(-1.0, 1.0);
break;
case 6: // -PI/2 and -width
affineTransform.translate(height, 0);
affineTransform.rotate(Math.PI / 2);
break;
case 7: // PI/2 and Flip
affineTransform.scale(-1.0, 1.0);
affineTransform.translate(-height, 0);
affineTransform.translate(0, width);
affineTransform.rotate(3 * Math.PI / 2);
break;
case 8: // PI / 2
affineTransform.translate(0, width);
affineTransform.rotate(3 * Math.PI / 2);
break;
default:
break;
}
AffineTransformOp affineTransformOp = new AffineTransformOp(affineTransform, AffineTransformOp.TYPE_BILINEAR);
BufferedImage destinationImage = new BufferedImage(read.getHeight(), read.getWidth(), read.getType());
destinationImage = affineTransformOp.filter(read, destinationImage);
return destinationImage;
} catch (Exception ex) {
ex.printStackTrace();
}
}
return read;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
2. 将图片转换为int数组
java中int为32位,颜色划分每八位分割,这个算法在C层会再次用到:
// 颜色组合有可能是ARGB, RGBA, ABGR, ARGB
// 如果需要将RGB转换为ARGB或RGBA,那么只需要在对应A通道上写入255即可
channel1 = (char) ((val >> 24) & 0xFF);
channel2 = (char) ((val >> 16) & 0xFF);
channel3 = (char) ((val >> 8) & 0xFF);
channel4 = (char) (val & 0xFF);
3. 将该数组传入jni层,并通过jni方法转换为C层可用的形式,(这里选用参数方式传递数据)
jint *const RGB_i = (env->GetIntArrayElements(java_rgb_array, &JNI_FALSE));
4. 创建cv::Mat
jint在jni_md.h中的定义就是int,所以这里可以直接强转使用(是否有更安全的办法)
CV_8UC4意思是:输入数组是8位无符号整型,4通道
cv::Mat src(input_img_height, input_img_width, CV_8UC4, (unsigned int*)RGB_i);
5. 将cv::Mat中的data成员变量(即储存图片颜色信息的数组),转换为bufferedImage接受的格式
// 这里转换为RGB格式数组
void matToBitmapArray(const cv::Mat &image, jint*_data) {
// BGR format
for (int i = 0; i < image.total(); i ++) {
char r = image.data[3 * i + 2];
char g = image.data[3 * i + 1];
char b = image.data[3 * i + 0];
_data[i] = (((jint)r << 16) & 0x00FF0000) +
(((jint)g << 8) & 0x0000FF00) + ((jint)b & 0x000000FF);
}
}
6. 将该数组拷贝回jvm(依然通过参数方式传递)
env->SetIntArrayRegion(outputBuffer, 0, width*height, _data);
7. 在java中,创建BufferedImage
// 这里注意TYPE_INT_RGB对应在jni层创建的RGB格式int数组
BufferedImage destImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
destImg.setRGB(0, 0, width, height, _data, 0, width);