parent
6e937843c9
commit
a5e9189101
@ -1,100 +0,0 @@ |
||||
package com.otaliastudios.cameraview.internal.utils; |
||||
|
||||
|
||||
import android.graphics.Bitmap; |
||||
import android.graphics.BitmapFactory; |
||||
import android.graphics.Canvas; |
||||
import android.graphics.Color; |
||||
import android.graphics.ImageFormat; |
||||
import android.graphics.Paint; |
||||
import android.graphics.PorterDuff; |
||||
import android.graphics.Rect; |
||||
import android.graphics.YuvImage; |
||||
import android.media.Image; |
||||
import android.media.ImageReader; |
||||
import android.os.Handler; |
||||
import android.os.Looper; |
||||
import android.view.Surface; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.test.ext.junit.runners.AndroidJUnit4; |
||||
import androidx.test.filters.SmallTest; |
||||
|
||||
import com.otaliastudios.cameraview.BaseTest; |
||||
import com.otaliastudios.cameraview.tools.Op; |
||||
import com.otaliastudios.cameraview.tools.SdkExclude; |
||||
|
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
|
||||
import static org.junit.Assert.assertNotNull; |
||||
|
||||
/** |
||||
* Starting from API 29, surface.lockCanvas() sets the surface format to RGBA_8888: |
||||
* https://github.com/aosp-mirror/platform_frameworks_base/blob/android10-release/core/jni/android_view_Surface.cpp#L215-L217 .
|
||||
* For this reason, acquireLatestImage crashes because we requested a different format. |
||||
*/ |
||||
@SdkExclude(minSdkVersion = 29) |
||||
@RunWith(AndroidJUnit4.class) |
||||
@SmallTest |
||||
public class ImageHelperTest extends BaseTest { |
||||
|
||||
@NonNull |
||||
private Image getImage() { |
||||
ImageReader reader = ImageReader.newInstance(100, 100, ImageFormat.YUV_420_888, 1); |
||||
Surface readerSurface = reader.getSurface(); |
||||
final Op<Image> imageOp = new Op<>(); |
||||
reader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { |
||||
@Override |
||||
public void onImageAvailable(ImageReader reader) { |
||||
Image image = reader.acquireLatestImage(); |
||||
if (image != null) imageOp.controller().end(image); |
||||
} |
||||
}, new Handler(Looper.getMainLooper())); |
||||
|
||||
// Write on reader surface.
|
||||
Canvas readerCanvas = readerSurface.lockCanvas(null); |
||||
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); |
||||
paint.setColor(Color.RED); |
||||
readerCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.MULTIPLY); |
||||
readerCanvas.drawCircle(50, 50, 50, paint); |
||||
readerSurface.unlockCanvasAndPost(readerCanvas); |
||||
|
||||
// Wait
|
||||
Image image = imageOp.await(5000); |
||||
assertNotNull(image); |
||||
return image; |
||||
} |
||||
|
||||
@Test |
||||
public void testImage() { |
||||
Image image = getImage(); |
||||
int width = image.getWidth(); |
||||
int height = image.getHeight(); |
||||
int bitsPerPixel = ImageFormat.getBitsPerPixel(ImageFormat.NV21); |
||||
int sizeBits = width * height * bitsPerPixel; |
||||
int sizeBytes = (int) Math.ceil(sizeBits / 8.0d); |
||||
byte[] bytes = new byte[sizeBytes]; |
||||
ImageHelper.convertToNV21(image, bytes); |
||||
image.close(); |
||||
|
||||
// Read the image
|
||||
YuvImage yuvImage = new YuvImage(bytes, ImageFormat.NV21, width, height, null); |
||||
ByteArrayOutputStream jpegStream = new ByteArrayOutputStream(); |
||||
yuvImage.compressToJpeg(new Rect(0, 0, width, height), 100, jpegStream); |
||||
byte[] jpegByteArray = jpegStream.toByteArray(); |
||||
Bitmap bitmap = BitmapFactory.decodeByteArray(jpegByteArray, 0, jpegByteArray.length); |
||||
assertNotNull(bitmap); |
||||
|
||||
// Wanted to do assertions on the color here but it doesn't work. There must be an issue
|
||||
// with how we are drawing the image in this test, since in real camera, the algorithm works well.
|
||||
// So for now let's just test that nothing crashes during this process.
|
||||
// int color = bitmap.getPixel(bitmap.getWidth() - 1, bitmap.getHeight() - 1);
|
||||
// assertEquals(Color.red(color), 255, 5);
|
||||
// assertEquals(Color.green(color), 0, 5);
|
||||
// assertEquals(Color.blue(color), 0, 5);
|
||||
// assertEquals(Color.alpha(color), 0, 5);
|
||||
} |
||||
} |
@ -0,0 +1,30 @@ |
||||
package com.otaliastudios.cameraview.frame; |
||||
|
||||
import android.media.Image; |
||||
import android.os.Build; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.RequiresApi; |
||||
|
||||
@RequiresApi(Build.VERSION_CODES.KITKAT) |
||||
public class ImageFrameManager extends FrameManager<Image> { |
||||
|
||||
public ImageFrameManager(int poolSize) { |
||||
super(poolSize, Image.class); |
||||
} |
||||
|
||||
@Override |
||||
protected void onFrameDataReleased(@NonNull Image data, boolean recycled) { |
||||
try { |
||||
data.close(); |
||||
} catch (Exception ignore) {} |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
protected Image onCloneFrameData(@NonNull Image data) { |
||||
throw new RuntimeException("Cannot freeze() an Image Frame. " + |
||||
"Please consider using the frame synchronously in your process() method, " + |
||||
"which also gives better performance."); |
||||
} |
||||
} |
@ -1,100 +0,0 @@ |
||||
package com.otaliastudios.cameraview.internal.utils; |
||||
|
||||
import android.graphics.ImageFormat; |
||||
import android.media.Image; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.RequiresApi; |
||||
|
||||
/** |
||||
* Conversions for {@link android.media.Image}s into byte arrays. |
||||
*/ |
||||
@RequiresApi(19) |
||||
public class ImageHelper { |
||||
|
||||
/** |
||||
* From https://stackoverflow.com/a/52740776/4288782 .
|
||||
* The result array should have a size that is at least 3/2 * w * h. |
||||
* This is correctly computed by {@link com.otaliastudios.cameraview.frame.FrameManager}. |
||||
* |
||||
* @param image input image |
||||
* @param result output array |
||||
*/ |
||||
public static void convertToNV21(@NonNull Image image, @NonNull byte[] result) { |
||||
if (image.getFormat() != ImageFormat.YUV_420_888) { |
||||
throw new IllegalStateException("CAn only convert from YUV_420_888."); |
||||
} |
||||
int width = image.getWidth(); |
||||
int height = image.getHeight(); |
||||
int ySize = width * height; |
||||
int uvSize = width * height / 4; |
||||
|
||||
ByteBuffer yBuffer = image.getPlanes()[0].getBuffer(); // Y
|
||||
ByteBuffer uBuffer = image.getPlanes()[1].getBuffer(); // U
|
||||
ByteBuffer vBuffer = image.getPlanes()[2].getBuffer(); // V
|
||||
|
||||
int rowStride = image.getPlanes()[0].getRowStride(); |
||||
|
||||
if (image.getPlanes()[0].getPixelStride() != 1) { |
||||
throw new AssertionError("Something wrong in convertToNV21"); |
||||
} |
||||
|
||||
int pos = 0; |
||||
|
||||
if (rowStride == width) { // likely
|
||||
yBuffer.get(result, 0, ySize); |
||||
pos += ySize; |
||||
} |
||||
else { |
||||
int yBufferPos = width - rowStride; // not an actual position
|
||||
for (; pos<ySize; pos+=width) { |
||||
yBufferPos += rowStride - width; |
||||
yBuffer.position(yBufferPos); |
||||
yBuffer.get(result, pos, width); |
||||
} |
||||
} |
||||
|
||||
rowStride = image.getPlanes()[2].getRowStride(); |
||||
int pixelStride = image.getPlanes()[2].getPixelStride(); |
||||
|
||||
if (rowStride != image.getPlanes()[1].getRowStride()) { |
||||
throw new AssertionError("Something wrong in convertToNV21"); |
||||
} |
||||
if (pixelStride != image.getPlanes()[1].getPixelStride()) { |
||||
throw new AssertionError("Something wrong in convertToNV21"); |
||||
} |
||||
|
||||
if (pixelStride == 2 && rowStride == width && uBuffer.get(0) == vBuffer.get(1)) { |
||||
// maybe V an U planes overlap as per NV21, which means vBuffer[1]
|
||||
// is alias of uBuffer[0]
|
||||
byte savePixel = vBuffer.get(1); |
||||
vBuffer.put(1, (byte)0); |
||||
if (uBuffer.get(0) == 0) { |
||||
vBuffer.put(1, (byte)255); |
||||
//noinspection ConstantConditions
|
||||
if (uBuffer.get(0) == 255) { |
||||
vBuffer.put(1, savePixel); |
||||
vBuffer.get(result, ySize, uvSize); |
||||
return; // shortcut
|
||||
} |
||||
} |
||||
|
||||
// unfortunately, the check failed. We must save U and V pixel by pixel
|
||||
vBuffer.put(1, savePixel); |
||||
} |
||||
|
||||
// other optimizations could check if (pixelStride == 1) or (pixelStride == 2),
|
||||
// but performance gain would be less significant
|
||||
|
||||
for (int row=0; row<height/2; row++) { |
||||
for (int col=0; col<width/2; col++) { |
||||
int vuPos = col*pixelStride + row*rowStride; |
||||
result[pos++] = vBuffer.get(vuPos); |
||||
result[pos++] = uBuffer.get(vuPos); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
Loading…
Reference in new issue