Search code examples
androidbitmaprenderscriptyuv

Corrupted images when using ScriptIntrinsicYuvToRGB (support lib) for NV21 to Bitmap


I am having issues when using ScriptIntrinsicYuvToRGB from the support library to convert images from NV21 format to Bitmaps (ARGB_8888). The code below can illustrate the problem.

Suppose I have the following 50x50 image (the one below is a screenshot from device, not actually 50x50):

enter image description here

Then if I convert said image to a Bitmap through the YuvImage#compressToJpeg + BitmapFactory.decodeByteArray:

YuvImage yuvImage = new YuvImage(example, android.graphics.ImageFormat.NV21, width, height, null);
ByteArrayOutputStream os = new ByteArrayOutputStream();
yuvImage.compressToJpeg(new Rect(0, 0, width, height), 100, os);
byte[] jpegByteArray = os.toByteArray();
return BitmapFactory.decodeByteArray(jpegByteArray, 0, jpegByteArray.length);

I get the expected image. But if I convert it through ScriptIntrinsicYuvToRGB as following:

RenderScript rs = RenderScript.create(context);

Type.Builder tb = new Type.Builder(rs, Element.createPixel(rs,
        Element.DataType.UNSIGNED_8, Element.DataKind.PIXEL_YUV));
tb.setX(width);
tb.setY(height);
tb.setYuvFormat(android.graphics.ImageFormat.NV21);
Allocation yuvAllocation = Allocation.createTyped(rs, tb.create(), Allocation.USAGE_SCRIPT);
yuvAllocation.copyFrom(example);

Type rgbType = Type.createXY(rs, Element.RGBA_8888(rs), width, height);
Allocation rgbAllocation = Allocation.createTyped(rs, rgbType);

ScriptIntrinsicYuvToRGB yuvToRgbScript = ScriptIntrinsicYuvToRGB.create(rs, Element.RGBA_8888(rs));
yuvToRgbScript.setInput(yuvAllocation);
yuvToRgbScript.forEach(rgbAllocation);

Bitmap convertedBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
rgbAllocation.copyTo(convertedBitmap);

I get the following corrupted image:

enter image description here

I have noticed that this happens with images of square sizes and never with powers of 2 (e.g. 64x64, 128x128, etc). Had I not tried square sizes I would not have noticed the problem, as some picture sizes such as 2592x1728 works ok. What am I missing?

Update: putting the code that generated the original image as requested:

int width = 50;
int height = 50;
int size = width * height;

byte[] example = new byte[size + size / 2];
for (int x = 0; x < width; x++) {
    for (int y = 0; y < height; y++) {
        example[y * width + x] = (byte) ((x*y / (float)size) * 255);
    }
}
for (int i = 0; i < size / 2; i++) {
    example[size + i] = (byte) (127);
}

Solution

  • The following code behaves in the wrong way:

    Type.Builder tb = new Type.Builder(rs, Element.createPixel(rs,
                        Element.DataType.UNSIGNED_8, Element.DataKind.PIXEL_YUV));
    tb.setX(width);
    tb.setY(height);
    tb.setYuvFormat(android.graphics.ImageFormat.NV21);
    yuvAllocation = Allocation.createTyped(rs, tb.create(), Allocation.USAGE_SCRIPT);
    

    If you replace it using a "raw" way of creating an allocation, the conversion will work:

    int expectedBytes = width * height *
                    ImageFormat.getBitsPerPixel(ImageFormat.NV21) / 8;
    
    Type.Builder yuvTypeBuilder = new Type.Builder(rs, Element.U8(rs))
                                                       .setX(expectedBytes);
    Type yuvType = yuvTypeBuilder.create();
    yuvAllocation = Allocation.createTyped(rs, yuvType, Allocation.USAGE_SCRIPT);
    

    It seems that, if you use the PIXEL_YUV definition, there is a size problem on non-multiple-of-16 dimensions. Still investigating on it.