YUV_420_888 Image to Bitmap using ScriptIntrinsicYuvToRGB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// yuv2rgb-lib.cc JNI code. | |
namespace { | |
void yuv420toNv21(int image_width, int image_height, const int8_t* y_buffer, | |
const int8_t* u_buffer, const int8_t* v_buffer, int y_pixel_stride, | |
int uv_pixel_stride, int y_row_stride, int uv_row_stride, | |
int8_t *nv21) { | |
// Copy Y channel. | |
for(int y = 0; y < image_height; ++y) { | |
int destOffset = image_width * y; | |
int yOffset = y * y_row_stride; | |
memcpy(nv21 + destOffset, y_buffer + yOffset, image_width); | |
} | |
if (v_buffer - u_buffer == sizeof(int8_t)) { | |
// format = nv21 | |
// TODO: If the format is VUVUVU & pixel stride == 1 we can simply the copy | |
// with memcpy. In Android Camera2 I have mostly come across UVUVUV packaging | |
// though. | |
} | |
// Copy UV Channel. | |
int idUV = image_width * image_height; | |
int uv_width = image_width / 2; | |
int uv_height = image_height / 2; | |
for(int y = 0; y < uv_height; ++y) { | |
int uvOffset = y * uv_row_stride; | |
for (int x = 0; x < uv_width; ++x) { | |
int bufferIndex = uvOffset + (x * uv_pixel_stride); | |
// V channel. | |
nv21[idUV++] = v_buffer[bufferIndex]; | |
// U channel. | |
nv21[idUV++] = u_buffer[bufferIndex]; | |
} | |
} | |
} | |
} // nampespace | |
extern "C" { | |
jboolean Java_com_example_androidcv_camera_processing_YuvConvertor_yuv420toNv21( | |
JNIEnv *env, jclass clazz, | |
jint image_width, jint image_height, jobject y_byte_buffer, | |
jobject u_byte_buffer, jobject v_byte_buffer, jint y_pixel_stride, | |
jint uv_pixel_stride, jint y_row_stride, jint uv_row_stride, | |
jbyteArray nv21_array) { | |
auto y_buffer = static_cast<jbyte*>(env->GetDirectBufferAddress(y_byte_buffer)); | |
auto u_buffer = static_cast<jbyte*>(env->GetDirectBufferAddress(u_byte_buffer)); | |
auto v_buffer = static_cast<jbyte*>(env->GetDirectBufferAddress(v_byte_buffer)); | |
jbyte* nv21 = env->GetByteArrayElements(nv21_array, nullptr); | |
if (nv21 == nullptr || y_buffer == nullptr || u_buffer == nullptr | |
|| v_buffer == nullptr) { | |
// Log this. | |
return false; | |
} | |
yuv420toNv21(image_width, image_height, y_buffer, u_buffer, v_buffer, | |
y_pixel_stride, uv_pixel_stride, y_row_stride, uv_row_stride, | |
nv21); | |
env->ReleaseByteArrayElements(nv21_array, nv21, 0); | |
return true; | |
} | |
} // extern "C" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class YuvConvertor { | |
private final Allocation in, out; | |
private final ScriptIntrinsicYuvToRGB script; | |
static { | |
System.loadLibrary("yuv2rgb-lib"); | |
} | |
public YuvConvertor(Context context, int width, int height) { | |
RenderScript rs = RenderScript.create(context); | |
this.script = ScriptIntrinsicYuvToRGB.create( | |
rs, Element.U8_4(rs)); | |
// NV21 YUV image of dimension 4 X 4 has following packing: | |
// YYYYYYYYYYYYYYYYVUVUVUVU | |
// With each pixel (of any channel) taking 8 bits. | |
int yuvByteArrayLength = (int) (width * height * 1.5f); | |
Type.Builder yuvType = new Type.Builder(rs, Element.U8(rs)) | |
.setX(yuvByteArrayLength); | |
this.in = Allocation.createTyped( | |
rs, yuvType.create(), Allocation.USAGE_SCRIPT); | |
Type.Builder rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs)) | |
.setX(width) | |
.setY(height); | |
this.out = Allocation.createTyped( | |
rs, rgbaType.create(), Allocation.USAGE_SCRIPT); | |
} | |
@Nullable | |
public Bitmap toBitmap(Image image) { | |
if (image.getFormat() != ImageFormat.YUV_420_888) { | |
throw new IllegalArgumentException("Only supports YUV_420_888."); | |
} | |
byte[] yuvByteArray = toNv21(image); | |
if (yuvByteArray == null) { | |
return null; | |
} | |
in.copyFrom(yuvByteArray); | |
script.setInput(in); | |
script.forEach(out); | |
// Allocate memory for the bitmap to return. If you have a reusable Bitmap | |
// I recommending using that. | |
Bitmap bitmap = Bitmap.createBitmap( | |
image.getWidth(), image.getHeight(), Config.ARGB_8888); | |
out.copyTo(bitmap); | |
return bitmap; | |
} | |
@Nullable | |
public static byte[] toNv21(Image image) { | |
byte[] nv21 = new byte[(int) (image.getWidth() * image.getHeight() * 1.5f)]; | |
if (!yuv420toNv21( | |
image.getWidth(), | |
image.getHeight(), | |
image.getPlanes()[0].getBuffer(), // Y buffer | |
image.getPlanes()[1].getBuffer(), // U buffer | |
image.getPlanes()[2].getBuffer(), // V buffer | |
image.getPlanes()[0].getPixelStride(), // Y pixel stride | |
image.getPlanes()[1].getPixelStride(), // U/V pixel stride | |
image.getPlanes()[0].getRowStride(), // Y row stride | |
image.getPlanes()[1].getRowStride(), // U/V row stride | |
nv21)) { | |
return null; | |
} | |
return nv21; | |
} | |
public static native boolean yuv420toNv21( | |
int imageWidth, | |
int imageHeight, | |
ByteBuffer yByteBuffer, | |
ByteBuffer uByteBuffer, | |
ByteBuffer vByteBuffer, | |
int yPixelStride, | |
int uvPixelStride, | |
int yRowStride, | |
int uvRowStride, | |
byte[] nv21Output); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment