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