parent
3486481494
commit
a5f940517b
@ -1,76 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview; |
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context; |
|
||||||
import android.content.pm.PackageManager; |
|
||||||
import android.graphics.Bitmap; |
|
||||||
import android.graphics.BitmapFactory; |
|
||||||
import android.graphics.Color; |
|
||||||
import android.graphics.Rect; |
|
||||||
import android.graphics.YuvImage; |
|
||||||
import android.support.test.filters.SmallTest; |
|
||||||
import android.support.test.runner.AndroidJUnit4; |
|
||||||
|
|
||||||
import org.junit.Test; |
|
||||||
import org.junit.runner.RunWith; |
|
||||||
import org.mockito.invocation.InvocationOnMock; |
|
||||||
import org.mockito.stubbing.Answer; |
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream; |
|
||||||
import java.io.OutputStream; |
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals; |
|
||||||
import static org.junit.Assert.assertFalse; |
|
||||||
import static org.junit.Assert.assertTrue; |
|
||||||
import static org.mockito.Matchers.any; |
|
||||||
import static org.mockito.Matchers.anyInt; |
|
||||||
import static org.mockito.Mockito.anyString; |
|
||||||
import static org.mockito.Mockito.mock; |
|
||||||
import static org.mockito.Mockito.when; |
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class) |
|
||||||
@SmallTest |
|
||||||
public class CropHelperTest extends BaseTest { |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testCropFromYuv() { |
|
||||||
testCropFromYuv(1600, 1600, AspectRatio.of(16, 9)); |
|
||||||
testCropFromYuv(1600, 1600, AspectRatio.of(9, 16)); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void testCropFromJpeg() { |
|
||||||
testCropFromJpeg(1600, 1600, AspectRatio.of(16, 9)); |
|
||||||
testCropFromJpeg(1600, 1600, AspectRatio.of(9, 16)); |
|
||||||
} |
|
||||||
|
|
||||||
private void testCropFromYuv(final int w, final int h, final AspectRatio target) { |
|
||||||
final boolean wider = target.toFloat() > ((float) w / (float) h); |
|
||||||
byte[] b = CropHelper.cropToJpeg(mockYuv(w, h), target, 100); |
|
||||||
Bitmap result = BitmapFactory.decodeByteArray(b, 0, b.length); |
|
||||||
|
|
||||||
// Assert.
|
|
||||||
AspectRatio ratio = AspectRatio.of(result.getWidth(), result.getHeight()); |
|
||||||
assertEquals(target, ratio); |
|
||||||
if (wider) { // width must match.
|
|
||||||
assertEquals(result.getWidth(), w); |
|
||||||
} else { |
|
||||||
assertEquals(result.getHeight(), h); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private void testCropFromJpeg(int w, int h, AspectRatio target) { |
|
||||||
final boolean wider = target.toFloat() > ((float) w / (float) h); |
|
||||||
byte[] b = CropHelper.cropToJpeg(mockJpeg(w, h), target, 100); |
|
||||||
Bitmap result = BitmapFactory.decodeByteArray(b, 0, b.length); |
|
||||||
|
|
||||||
// Assert.
|
|
||||||
AspectRatio ratio = AspectRatio.of(result.getWidth(), result.getHeight()); |
|
||||||
assertEquals(target, ratio); |
|
||||||
if (wider) { // width must match.
|
|
||||||
assertEquals(result.getWidth(), w); |
|
||||||
} else { |
|
||||||
assertEquals(result.getHeight(), h); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,43 @@ |
|||||||
|
package com.otaliastudios.cameraview; |
||||||
|
|
||||||
|
|
||||||
|
import android.location.Location; |
||||||
|
import android.support.test.filters.SmallTest; |
||||||
|
import android.support.test.runner.AndroidJUnit4; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.mockito.Mockito; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals; |
||||||
|
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class) |
||||||
|
@SmallTest |
||||||
|
public class PictureResultTest extends BaseTest { |
||||||
|
|
||||||
|
private PictureResult result = new PictureResult(); |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testResult() { |
||||||
|
int rotation = 90; |
||||||
|
Size size = new Size(20, 120); |
||||||
|
byte[] jpeg = new byte[]{2, 4, 1, 5, 2}; |
||||||
|
Location location = Mockito.mock(Location.class); |
||||||
|
boolean isSnapshot = true; |
||||||
|
|
||||||
|
result.rotation = rotation; |
||||||
|
result.size = size; |
||||||
|
result.jpeg = jpeg; |
||||||
|
result.location = location; |
||||||
|
result.isSnapshot = isSnapshot; |
||||||
|
|
||||||
|
assertEquals(result.getRotation(), rotation); |
||||||
|
assertEquals(result.getSize(), size); |
||||||
|
assertEquals(result.getJpeg(), jpeg); |
||||||
|
assertEquals(result.getLocation(), location); |
||||||
|
assertEquals(result.isSnapshot(), isSnapshot); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,64 @@ |
|||||||
|
package com.otaliastudios.cameraview; |
||||||
|
|
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.content.pm.PackageManager; |
||||||
|
import android.graphics.Bitmap; |
||||||
|
import android.graphics.BitmapFactory; |
||||||
|
import android.graphics.Color; |
||||||
|
import android.graphics.Rect; |
||||||
|
import android.graphics.YuvImage; |
||||||
|
import android.support.test.filters.SmallTest; |
||||||
|
import android.support.test.runner.AndroidJUnit4; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.mockito.invocation.InvocationOnMock; |
||||||
|
import org.mockito.stubbing.Answer; |
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream; |
||||||
|
import java.io.OutputStream; |
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals; |
||||||
|
import static org.junit.Assert.assertFalse; |
||||||
|
import static org.junit.Assert.assertNotEquals; |
||||||
|
import static org.junit.Assert.assertTrue; |
||||||
|
import static org.mockito.Matchers.any; |
||||||
|
import static org.mockito.Matchers.anyInt; |
||||||
|
import static org.mockito.Mockito.anyString; |
||||||
|
import static org.mockito.Mockito.mock; |
||||||
|
import static org.mockito.Mockito.when; |
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class) |
||||||
|
@SmallTest |
||||||
|
public class YuvHelperTest extends BaseTest { |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testCrop() { |
||||||
|
testCrop(new Size(1600, 1600), AspectRatio.of(16, 16)); |
||||||
|
testCrop(new Size(1600, 1600), AspectRatio.of(16, 9)); |
||||||
|
testCrop(new Size(1600, 1600), AspectRatio.of(9, 16)); |
||||||
|
} |
||||||
|
|
||||||
|
private void testCrop(final Size inSize, final AspectRatio outRatio) { |
||||||
|
AspectRatio inRatio = AspectRatio.of(inSize.getWidth(), inSize.getHeight()); |
||||||
|
Rect out = YuvHelper.computeCrop(inSize, outRatio); |
||||||
|
Size outSize = new Size(out.width(), out.height()); |
||||||
|
assertTrue(outRatio.matches(outSize)); |
||||||
|
|
||||||
|
if (outRatio.matches(inSize)) { |
||||||
|
// They are equal.
|
||||||
|
assertEquals(outSize.getWidth(), inSize.getWidth()); |
||||||
|
assertEquals(outSize.getHeight(), inSize.getHeight()); |
||||||
|
} else if (outRatio.toFloat() > inRatio.toFloat()) { |
||||||
|
// Width must match.
|
||||||
|
assertEquals(outSize.getWidth(), inSize.getWidth()); |
||||||
|
assertNotEquals(outSize.getHeight(), inSize.getHeight()); |
||||||
|
} else { |
||||||
|
// Height must match.
|
||||||
|
assertEquals(outSize.getHeight(), inSize.getHeight()); |
||||||
|
assertNotEquals(outSize.getWidth(), inSize.getWidth()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,85 @@ |
|||||||
|
package com.otaliastudios.cameraview; |
||||||
|
|
||||||
|
import android.location.Location; |
||||||
|
import android.support.annotation.NonNull; |
||||||
|
import android.support.annotation.Nullable; |
||||||
|
|
||||||
|
/** |
||||||
|
* Wraps the picture captured by {@link CameraView#takePicture()} or |
||||||
|
* {@link CameraView#takePictureSnapshot()}. |
||||||
|
*/ |
||||||
|
public class PictureResult { |
||||||
|
|
||||||
|
boolean isSnapshot; |
||||||
|
Location location; |
||||||
|
int rotation; |
||||||
|
Size size; |
||||||
|
byte[] jpeg; |
||||||
|
|
||||||
|
PictureResult() {} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns whether this result comes from a snapshot. |
||||||
|
* |
||||||
|
* @return whether this is a snapshot |
||||||
|
*/ |
||||||
|
public boolean isSnapshot() { |
||||||
|
return isSnapshot; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns geographic information for this picture, if any. |
||||||
|
* If it was set, it is also present in the file metadata. |
||||||
|
* |
||||||
|
* @return a nullable Location |
||||||
|
*/ |
||||||
|
@Nullable |
||||||
|
public Location getLocation() { |
||||||
|
return location; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the clock-wise rotation that should be applied to the |
||||||
|
* picture before displaying. If it is non-zero, it is also present |
||||||
|
* in the jpeg EXIF metadata. |
||||||
|
* |
||||||
|
* @return the clock-wise rotation |
||||||
|
*/ |
||||||
|
public int getRotation() { |
||||||
|
return rotation; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the size of the picture after the rotation is applied. |
||||||
|
* |
||||||
|
* @return the Size of this picture |
||||||
|
*/ |
||||||
|
@NonNull |
||||||
|
public Size getSize() { |
||||||
|
return size; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the raw jpeg, ready to be saved to file. |
||||||
|
* |
||||||
|
* @return the jpeg stream |
||||||
|
*/ |
||||||
|
@NonNull |
||||||
|
public byte[] getJpeg() { |
||||||
|
return jpeg; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Shorthand for {@link CameraUtils#decodeBitmap(byte[], int, int, BitmapCallback)}. |
||||||
|
* Decodes this picture on a background thread and posts the result in the UI thread using |
||||||
|
* the given callback. |
||||||
|
* |
||||||
|
* @param maxWidth the max. width of final bitmap |
||||||
|
* @param maxHeight the max. height of final bitmap |
||||||
|
* @param callback a callback to be notified of image decoding |
||||||
|
*/ |
||||||
|
public void asBitmap(int maxWidth, int maxHeight, BitmapCallback callback) { |
||||||
|
CameraUtils.decodeBitmap(getJpeg(), maxWidth, maxHeight, callback); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
package com.otaliastudios.cameraview; |
||||||
|
|
||||||
|
import android.graphics.Bitmap; |
||||||
|
import android.support.annotation.UiThread; |
||||||
|
|
||||||
|
/** |
||||||
|
* Receives callbacks about a bitmap decoding operation. |
||||||
|
*/ |
||||||
|
public interface BitmapCallback { |
||||||
|
|
||||||
|
/** |
||||||
|
* Notifies that the bitmap was succesfully decoded. |
||||||
|
* This is run on the UI thread. |
||||||
|
* |
||||||
|
* @param bitmap decoded bitmap |
||||||
|
*/ |
||||||
|
@UiThread |
||||||
|
void onBitmapReady(Bitmap bitmap); |
||||||
|
} |
@ -1,50 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview; |
|
||||||
|
|
||||||
import android.graphics.Bitmap; |
|
||||||
import android.graphics.Rect; |
|
||||||
import android.graphics.YuvImage; |
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream; |
|
||||||
|
|
||||||
class CropHelper { |
|
||||||
|
|
||||||
|
|
||||||
static byte[] cropToJpeg(YuvImage yuv, AspectRatio targetRatio, int jpegCompression) { |
|
||||||
Rect crop = computeCrop(yuv.getWidth(), yuv.getHeight(), targetRatio); |
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream(); |
|
||||||
yuv.compressToJpeg(crop, jpegCompression, out); |
|
||||||
return out.toByteArray(); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
// This reads a rotated Bitmap thanks to CameraUtils. Then crops and returns a byte array.
|
|
||||||
// In doing so, EXIF data is deleted.
|
|
||||||
static byte[] cropToJpeg(byte[] jpeg, AspectRatio targetRatio, int jpegCompression) { |
|
||||||
|
|
||||||
Bitmap image = CameraUtils.decodeBitmap(jpeg, Integer.MAX_VALUE, Integer.MAX_VALUE); |
|
||||||
Rect cropRect = computeCrop(image.getWidth(), image.getHeight(), targetRatio); |
|
||||||
Bitmap crop = Bitmap.createBitmap(image, cropRect.left, cropRect.top, cropRect.width(), cropRect.height()); |
|
||||||
image.recycle(); |
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream(); |
|
||||||
crop.compress(Bitmap.CompressFormat.JPEG, jpegCompression, out); |
|
||||||
crop.recycle(); |
|
||||||
return out.toByteArray(); |
|
||||||
} |
|
||||||
|
|
||||||
private static Rect computeCrop(int currentWidth, int currentHeight, AspectRatio targetRatio) { |
|
||||||
AspectRatio currentRatio = AspectRatio.of(currentWidth, currentHeight); |
|
||||||
int x, y, width, height; |
|
||||||
if (currentRatio.toFloat() > targetRatio.toFloat()) { |
|
||||||
height = currentHeight; |
|
||||||
width = (int) (height * targetRatio.toFloat()); |
|
||||||
y = 0; |
|
||||||
x = (currentWidth - width) / 2; |
|
||||||
} else { |
|
||||||
width = currentWidth; |
|
||||||
height = (int) (width / targetRatio.toFloat()); |
|
||||||
y = (currentHeight - height) / 2; |
|
||||||
x = 0; |
|
||||||
} |
|
||||||
return new Rect(x, y, x + width, y + height); |
|
||||||
} |
|
||||||
} |
|
@ -1,13 +1,40 @@ |
|||||||
package com.otaliastudios.cameraview; |
package com.otaliastudios.cameraview; |
||||||
|
|
||||||
class RotationHelper { |
import android.graphics.Rect; |
||||||
|
|
||||||
|
class YuvHelper { |
||||||
|
|
||||||
static byte[] rotate(final byte[] yuv, final int width, final int height, final int rotation) { |
static Rect computeCrop(Size currentSize, AspectRatio targetRatio) { |
||||||
|
int currentWidth = currentSize.getWidth(); |
||||||
|
int currentHeight = currentSize.getHeight(); |
||||||
|
if (targetRatio.matches(currentSize)) { |
||||||
|
return new Rect(0, 0, currentWidth, currentHeight); |
||||||
|
} |
||||||
|
|
||||||
|
// They are not equal. Compute.
|
||||||
|
AspectRatio currentRatio = AspectRatio.of(currentWidth, currentHeight); |
||||||
|
int x, y, width, height; |
||||||
|
if (currentRatio.toFloat() > targetRatio.toFloat()) { |
||||||
|
height = currentHeight; |
||||||
|
width = (int) (height * targetRatio.toFloat()); |
||||||
|
y = 0; |
||||||
|
x = (currentWidth - width) / 2; |
||||||
|
} else { |
||||||
|
width = currentWidth; |
||||||
|
height = (int) (width / targetRatio.toFloat()); |
||||||
|
y = (currentHeight - height) / 2; |
||||||
|
x = 0; |
||||||
|
} |
||||||
|
return new Rect(x, y, x + width, y + height); |
||||||
|
} |
||||||
|
|
||||||
|
static byte[] rotate(final byte[] yuv, final Size size, final int rotation) { |
||||||
if (rotation == 0) return yuv; |
if (rotation == 0) return yuv; |
||||||
if (rotation % 90 != 0 || rotation < 0 || rotation > 270) { |
if (rotation % 90 != 0 || rotation < 0 || rotation > 270) { |
||||||
throw new IllegalArgumentException("0 <= rotation < 360, rotation % 90 == 0"); |
throw new IllegalArgumentException("0 <= rotation < 360, rotation % 90 == 0"); |
||||||
} |
} |
||||||
|
final int width = size.getWidth(); |
||||||
|
final int height = size.getHeight(); |
||||||
final byte[] output = new byte[yuv.length]; |
final byte[] output = new byte[yuv.length]; |
||||||
final int frameSize = width * height; |
final int frameSize = width * height; |
||||||
final boolean swap = rotation % 180 != 0; |
final boolean swap = rotation % 180 != 0; |
Loading…
Reference in new issue