parent
a144b375af
commit
2500b81b78
@ -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; |
||||
|
||||
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 % 90 != 0 || rotation < 0 || rotation > 270) { |
||||
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 int frameSize = width * height; |
||||
final boolean swap = rotation % 180 != 0; |
Loading…
Reference in new issue