Option to choose the picture output size (#99)
* Add SizeSelectors and tests * Implement SizeSelectors in CameraController * XML attrs, improve AspectRatio cache * AspectRatio tests * Flip sizes before passing to selectors * Add README info * Fix preview sizing bug * Nits * Fix #98pull/105/head
parent
6b9affc435
commit
dcf5ef4120
@ -0,0 +1,22 @@ |
|||||||
|
package com.otaliastudios.cameraview; |
||||||
|
|
||||||
|
import android.support.annotation.NonNull; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* A size selector receives a list of {@link Size}s and returns another list with |
||||||
|
* sizes that are considered acceptable. |
||||||
|
*/ |
||||||
|
public interface SizeSelector { |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns a list of acceptable sizes from the given input. |
||||||
|
* The final size will be the first element in the output list. |
||||||
|
* |
||||||
|
* @param source input list |
||||||
|
* @return output list |
||||||
|
*/ |
||||||
|
@NonNull |
||||||
|
List<Size> select(@NonNull List<Size> source); |
||||||
|
} |
@ -0,0 +1,276 @@ |
|||||||
|
package com.otaliastudios.cameraview; |
||||||
|
|
||||||
|
import android.support.annotation.NonNull; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* Static utilities to create, join and merge {@link SizeSelector}s instances. |
||||||
|
*/ |
||||||
|
public class SizeSelectors { |
||||||
|
|
||||||
|
/** |
||||||
|
* A size constraint to easily filter out |
||||||
|
* sizes in a list. |
||||||
|
*/ |
||||||
|
public interface Filter { |
||||||
|
boolean accepts(Size size); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns a new {@link SizeSelector} with the given {@link Filter}. |
||||||
|
* This kind of selector will respect the order in the source array. |
||||||
|
* |
||||||
|
* @param filter a filter |
||||||
|
* @return a new selector |
||||||
|
*/ |
||||||
|
public static SizeSelector withFilter(@NonNull Filter filter) { |
||||||
|
return new FilterSelector(filter); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns a new {@link SizeSelector} that keeps only sizes |
||||||
|
* whose width is at most equal to the given width. |
||||||
|
* |
||||||
|
* @param width the max width |
||||||
|
* @return a new selector |
||||||
|
*/ |
||||||
|
public static SizeSelector maxWidth(final int width) { |
||||||
|
return withFilter(new Filter() { |
||||||
|
@Override |
||||||
|
public boolean accepts(Size size) { |
||||||
|
return size.getWidth() <= width; |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns a new {@link SizeSelector} that keeps only sizes |
||||||
|
* whose width is at least equal to the given width. |
||||||
|
* |
||||||
|
* @param width the min width |
||||||
|
* @return a new selector |
||||||
|
*/ |
||||||
|
public static SizeSelector minWidth(final int width) { |
||||||
|
return withFilter(new Filter() { |
||||||
|
@Override |
||||||
|
public boolean accepts(Size size) { |
||||||
|
return size.getWidth() >= width; |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns a new {@link SizeSelector} that keeps only sizes |
||||||
|
* whose height is at most equal to the given height. |
||||||
|
* |
||||||
|
* @param height the max height |
||||||
|
* @return a new selector |
||||||
|
*/ |
||||||
|
public static SizeSelector maxHeight(final int height) { |
||||||
|
return withFilter(new Filter() { |
||||||
|
@Override |
||||||
|
public boolean accepts(Size size) { |
||||||
|
return size.getHeight() <= height; |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns a new {@link SizeSelector} that keeps only sizes |
||||||
|
* whose height is at least equal to the given height. |
||||||
|
* |
||||||
|
* @param height the min height |
||||||
|
* @return a new selector |
||||||
|
*/ |
||||||
|
public static SizeSelector minHeight(final int height) { |
||||||
|
return withFilter(new Filter() { |
||||||
|
@Override |
||||||
|
public boolean accepts(Size size) { |
||||||
|
return size.getHeight() >= height; |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns a new {@link SizeSelector} that keeps only sizes |
||||||
|
* which respect the given {@link AspectRatio}. You can pass a tolerance |
||||||
|
* value to include aspect ratios that are slightly different. |
||||||
|
* |
||||||
|
* @param ratio the desired aspect ratio |
||||||
|
* @param delta a small tolerance value |
||||||
|
* @return a new selector |
||||||
|
*/ |
||||||
|
public static SizeSelector aspectRatio(AspectRatio ratio, final float delta) { |
||||||
|
final float desired = ratio.toFloat(); |
||||||
|
return withFilter(new Filter() { |
||||||
|
@Override |
||||||
|
public boolean accepts(Size size) { |
||||||
|
float candidate = AspectRatio.of(size.getWidth(), size.getHeight()).toFloat(); |
||||||
|
return candidate >= desired - delta && candidate <= desired + delta; |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns a {@link SizeSelector} that will order sizes from |
||||||
|
* the biggest to the smallest. This means that the biggest size will be taken. |
||||||
|
* |
||||||
|
* @return a new selector |
||||||
|
*/ |
||||||
|
public static SizeSelector biggest() { |
||||||
|
return new SizeSelector() { |
||||||
|
@NonNull |
||||||
|
@Override |
||||||
|
public List<Size> select(@NonNull List<Size> source) { |
||||||
|
Collections.sort(source); |
||||||
|
Collections.reverse(source); |
||||||
|
return source; |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns a {@link SizeSelector} that will order sizes from |
||||||
|
* the smallest to the biggest. This means that the smallest size will be taken. |
||||||
|
* |
||||||
|
* @return a new selector |
||||||
|
*/ |
||||||
|
public static SizeSelector smallest() { |
||||||
|
return new SizeSelector() { |
||||||
|
@NonNull |
||||||
|
@Override |
||||||
|
public List<Size> select(@NonNull List<Size> source) { |
||||||
|
Collections.sort(source); |
||||||
|
return source; |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns a new {@link SizeSelector} that keeps only sizes |
||||||
|
* whose area is at most equal to the given area in pixels. |
||||||
|
* |
||||||
|
* @param area the max area |
||||||
|
* @return a new selector |
||||||
|
*/ |
||||||
|
public static SizeSelector maxArea(final int area) { |
||||||
|
return withFilter(new Filter() { |
||||||
|
@Override |
||||||
|
public boolean accepts(Size size) { |
||||||
|
return size.getHeight() * size.getWidth() <= area; |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns a new {@link SizeSelector} that keeps only sizes |
||||||
|
* whose area is at least equal to the given area in pixels. |
||||||
|
* |
||||||
|
* @param area the min area |
||||||
|
* @return a new selector |
||||||
|
*/ |
||||||
|
public static SizeSelector minArea(final int area) { |
||||||
|
return withFilter(new Filter() { |
||||||
|
@Override |
||||||
|
public boolean accepts(Size size) { |
||||||
|
return size.getHeight() * size.getWidth() >= area; |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Joins all the given selectors to create a new one that returns |
||||||
|
* the intersection of all the inputs. Basically, all constraints are |
||||||
|
* respected. |
||||||
|
* |
||||||
|
* Keep in mind there is good chance that the final list will be empty. |
||||||
|
* |
||||||
|
* @param selectors input selectors |
||||||
|
* @return a new selector |
||||||
|
*/ |
||||||
|
public static SizeSelector and(SizeSelector... selectors) { |
||||||
|
return new AndSelector(selectors); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new {@link SizeSelector} that 'or's the given filters. |
||||||
|
* If the first selector returns an empty list, the next selector is queried, |
||||||
|
* and so on until a non-empty list is found. |
||||||
|
* |
||||||
|
* @param selectors input selectors |
||||||
|
* @return a new selector |
||||||
|
*/ |
||||||
|
public static SizeSelector or(SizeSelector... selectors) { |
||||||
|
return new OrSelector(selectors); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
//region private utilities
|
||||||
|
|
||||||
|
private static class FilterSelector implements SizeSelector { |
||||||
|
|
||||||
|
private Filter constraint; |
||||||
|
|
||||||
|
private FilterSelector(@NonNull Filter constraint) { |
||||||
|
this.constraint = constraint; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
@NonNull |
||||||
|
public List<Size> select(@NonNull List<Size> source) { |
||||||
|
List<Size> sizes = new ArrayList<>(); |
||||||
|
for (Size size : source) { |
||||||
|
if (constraint.accepts(size)) { |
||||||
|
sizes.add(size); |
||||||
|
} |
||||||
|
} |
||||||
|
return sizes; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static class AndSelector implements SizeSelector { |
||||||
|
|
||||||
|
private SizeSelector[] values; |
||||||
|
|
||||||
|
private AndSelector(@NonNull SizeSelector... values) { |
||||||
|
this.values = values; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
@NonNull |
||||||
|
public List<Size> select(@NonNull List<Size> source) { |
||||||
|
List<Size> temp = source; |
||||||
|
for (SizeSelector selector : values) { |
||||||
|
temp = selector.select(temp); |
||||||
|
} |
||||||
|
return temp; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static class OrSelector implements SizeSelector { |
||||||
|
|
||||||
|
private SizeSelector[] values; |
||||||
|
|
||||||
|
private OrSelector(@NonNull SizeSelector... values) { |
||||||
|
this.values = values; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
@NonNull |
||||||
|
public List<Size> select(@NonNull List<Size> source) { |
||||||
|
List<Size> temp = null; |
||||||
|
for (SizeSelector selector : values) { |
||||||
|
temp = selector.select(source); |
||||||
|
if (!temp.isEmpty()) { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
return temp == null ? new ArrayList<Size>() : temp; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
//endregion
|
||||||
|
} |
@ -0,0 +1,166 @@ |
|||||||
|
package com.otaliastudios.cameraview; |
||||||
|
|
||||||
|
|
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals; |
||||||
|
import static org.junit.Assert.assertTrue; |
||||||
|
import static org.mockito.Mockito.*; |
||||||
|
|
||||||
|
public class SizeSelectorsTest { |
||||||
|
|
||||||
|
private List<Size> input; |
||||||
|
|
||||||
|
@Before |
||||||
|
public void setUp() { |
||||||
|
input = Arrays.asList( |
||||||
|
new Size(100, 200), |
||||||
|
new Size(150, 300), |
||||||
|
new Size(600, 900), |
||||||
|
new Size(600, 600), |
||||||
|
new Size(1600, 900), |
||||||
|
new Size(30, 40), |
||||||
|
new Size(40, 30), |
||||||
|
new Size(2000, 4000) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testWithFilter() { |
||||||
|
SizeSelector selector = SizeSelectors.withFilter(new SizeSelectors.Filter() { |
||||||
|
@Override |
||||||
|
public boolean accepts(Size size) { |
||||||
|
return size.getWidth() == 600; |
||||||
|
} |
||||||
|
}); |
||||||
|
List<Size> list = selector.select(input); |
||||||
|
assertEquals(list.size(), 2); |
||||||
|
assertEquals(list.get(0), new Size(600, 900)); |
||||||
|
assertEquals(list.get(1), new Size(600, 600)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testMaxWidth() { |
||||||
|
SizeSelector selector = SizeSelectors.maxWidth(50); |
||||||
|
List<Size> list = selector.select(input); |
||||||
|
assertEquals(list.size(), 2); |
||||||
|
assertEquals(list.get(0), new Size(30, 40)); |
||||||
|
assertEquals(list.get(1), new Size(40, 30)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testMinWidth() { |
||||||
|
SizeSelector selector = SizeSelectors.minWidth(1000); |
||||||
|
List<Size> list = selector.select(input); |
||||||
|
assertEquals(list.size(), 2); |
||||||
|
assertEquals(list.get(0), new Size(1600, 900)); |
||||||
|
assertEquals(list.get(1), new Size(2000, 4000)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testMaxHeight() { |
||||||
|
SizeSelector selector = SizeSelectors.maxHeight(50); |
||||||
|
List<Size> list = selector.select(input); |
||||||
|
assertEquals(list.size(), 2); |
||||||
|
assertEquals(list.get(0), new Size(30, 40)); |
||||||
|
assertEquals(list.get(1), new Size(40, 30)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testMinHeight() { |
||||||
|
SizeSelector selector = SizeSelectors.minHeight(1000); |
||||||
|
List<Size> list = selector.select(input); |
||||||
|
assertEquals(list.size(), 1); |
||||||
|
assertEquals(list.get(0), new Size(2000, 4000)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testAspectRatio() { |
||||||
|
SizeSelector selector = SizeSelectors.aspectRatio(AspectRatio.of(16, 9), 0); |
||||||
|
List<Size> list = selector.select(input); |
||||||
|
assertEquals(list.size(), 1); |
||||||
|
assertEquals(list.get(0), new Size(1600, 900)); |
||||||
|
|
||||||
|
selector = SizeSelectors.aspectRatio(AspectRatio.of(1, 2), 0); |
||||||
|
list = selector.select(input); |
||||||
|
assertEquals(list.size(), 3); |
||||||
|
assertEquals(list.get(0), new Size(100, 200)); |
||||||
|
assertEquals(list.get(1), new Size(150, 300)); |
||||||
|
assertEquals(list.get(2), new Size(2000, 4000)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testMax() { |
||||||
|
SizeSelector selector = SizeSelectors.biggest(); |
||||||
|
List<Size> list = selector.select(input); |
||||||
|
assertEquals(list.size(), input.size()); |
||||||
|
assertEquals(list.get(0), new Size(2000, 4000)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testMin() { |
||||||
|
SizeSelector selector = SizeSelectors.smallest(); |
||||||
|
List<Size> list = selector.select(input); |
||||||
|
assertEquals(list.size(), input.size()); |
||||||
|
assertTrue(list.get(0).equals(new Size(30, 40)) || list.get(0).equals(new Size(40, 30))); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testMaxArea() { |
||||||
|
SizeSelector selector = SizeSelectors.maxArea(100 * 100); |
||||||
|
List<Size> list = selector.select(input); |
||||||
|
assertEquals(list.size(), 2); |
||||||
|
assertEquals(list.get(0), new Size(30, 40)); |
||||||
|
assertEquals(list.get(1), new Size(40, 30)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testMinArea() { |
||||||
|
SizeSelector selector = SizeSelectors.minArea(1000 * 1000); // 1 MP
|
||||||
|
List<Size> list = selector.select(input); |
||||||
|
assertEquals(list.size(), 2); |
||||||
|
assertEquals(list.get(0), new Size(1600, 900)); |
||||||
|
assertEquals(list.get(1), new Size(2000, 4000)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testAnd() { |
||||||
|
SizeSelector selector = SizeSelectors.and( |
||||||
|
SizeSelectors.aspectRatio(AspectRatio.of(1, 2), 0), |
||||||
|
SizeSelectors.maxWidth(100) |
||||||
|
); |
||||||
|
List<Size> list = selector.select(input); |
||||||
|
assertEquals(list.size(), 1); |
||||||
|
assertEquals(list.get(0), new Size(100, 200)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testOrNotPassed() { |
||||||
|
SizeSelector mock = mock(SizeSelector.class); |
||||||
|
SizeSelector selector = SizeSelectors.or( |
||||||
|
SizeSelectors.aspectRatio(AspectRatio.of(1, 2), 0), |
||||||
|
mock |
||||||
|
); |
||||||
|
|
||||||
|
// The first gives some result so the second is not queried.
|
||||||
|
selector.select(input); |
||||||
|
verify(mock, never()).select(anyListOf(Size.class)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testOrPassed() { |
||||||
|
SizeSelector mock = mock(SizeSelector.class); |
||||||
|
SizeSelector selector = SizeSelectors.or( |
||||||
|
SizeSelectors.minHeight(600000), |
||||||
|
mock |
||||||
|
); |
||||||
|
|
||||||
|
// The first gives no result so the second is queried.
|
||||||
|
selector.select(input); |
||||||
|
verify(mock, times(1)).select(anyListOf(Size.class)); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue