| /* |
| * Copyright 2023 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package androidx.camera.core.internal; |
| |
| import static androidx.camera.core.impl.utils.AspectRatioUtil.ASPECT_RATIO_16_9; |
| import static androidx.camera.core.impl.utils.AspectRatioUtil.ASPECT_RATIO_3_4; |
| import static androidx.camera.core.impl.utils.AspectRatioUtil.ASPECT_RATIO_4_3; |
| import static androidx.camera.core.impl.utils.AspectRatioUtil.ASPECT_RATIO_9_16; |
| import static androidx.camera.core.impl.utils.AspectRatioUtil.hasMatchingAspectRatio; |
| |
| import android.graphics.ImageFormat; |
| import android.util.Pair; |
| import android.util.Rational; |
| import android.util.Size; |
| import android.view.Surface; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.annotation.RequiresApi; |
| import androidx.camera.core.AspectRatio; |
| import androidx.camera.core.CameraSelector; |
| import androidx.camera.core.Logger; |
| import androidx.camera.core.impl.CameraInfoInternal; |
| import androidx.camera.core.impl.ImageOutputConfig; |
| import androidx.camera.core.impl.UseCaseConfig; |
| import androidx.camera.core.impl.utils.AspectRatioUtil; |
| import androidx.camera.core.impl.utils.CameraOrientationUtil; |
| import androidx.camera.core.impl.utils.CompareSizesByArea; |
| import androidx.camera.core.internal.utils.SizeUtil; |
| import androidx.camera.core.resolutionselector.AspectRatioStrategy; |
| import androidx.camera.core.resolutionselector.ResolutionFilter; |
| import androidx.camera.core.resolutionselector.ResolutionSelector; |
| import androidx.camera.core.resolutionselector.ResolutionStrategy; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * The supported output sizes collector to help collect the available resolution candidate list |
| * according to the use case config and the following settings in {@link ResolutionSelector}: |
| * |
| * <ul> |
| * <li>Aspect ratio strategy |
| * <li>Resolution strategy |
| * <li>Custom resolution filter |
| * <li>High resolution enabled flags |
| * </ul> |
| */ |
| @RequiresApi(21) |
| public class SupportedOutputSizesSorter { |
| private static final String TAG = "SupportedOutputSizesCollector"; |
| private final CameraInfoInternal mCameraInfoInternal; |
| private final int mSensorOrientation; |
| private final int mLensFacing; |
| private final Rational mFullFovRatio; |
| private final SupportedOutputSizesSorterLegacy mSupportedOutputSizesSorterLegacy; |
| |
| SupportedOutputSizesSorter(@NonNull CameraInfoInternal cameraInfoInternal, |
| @Nullable Size activeArraySize) { |
| mCameraInfoInternal = cameraInfoInternal; |
| mSensorOrientation = mCameraInfoInternal.getSensorRotationDegrees(); |
| mLensFacing = mCameraInfoInternal.getLensFacing(); |
| mFullFovRatio = activeArraySize != null ? calculateFullFovRatioFromActiveArraySize( |
| activeArraySize) : calculateFullFovRatioFromSupportedOutputSizes( |
| mCameraInfoInternal); |
| mSupportedOutputSizesSorterLegacy = |
| new SupportedOutputSizesSorterLegacy(cameraInfoInternal, mFullFovRatio); |
| } |
| |
| /** |
| * Calculates the full FOV ratio by the active array size. |
| */ |
| @NonNull |
| private Rational calculateFullFovRatioFromActiveArraySize(@NonNull Size activeArraySize) { |
| return new Rational(activeArraySize.getWidth(), activeArraySize.getHeight()); |
| } |
| |
| /** |
| * Calculates the full FOV ratio by the output sizes retrieved from CameraInfoInternal. |
| * |
| * <p>For most devices, the full FOV ratio should match the aspect ratio of the max supported |
| * output sizes. The active pixel array info is not used because it may cause robolectric |
| * test to fail if it is not set in the test environment. |
| */ |
| @Nullable |
| private Rational calculateFullFovRatioFromSupportedOutputSizes( |
| @NonNull CameraInfoInternal cameraInfoInternal) { |
| List<Size> jpegOutputSizes = cameraInfoInternal.getSupportedResolutions(ImageFormat.JPEG); |
| if (jpegOutputSizes.isEmpty()) { |
| return null; |
| } |
| Size maxSize = Collections.max(jpegOutputSizes, new CompareSizesByArea()); |
| return new Rational(maxSize.getWidth(), maxSize.getHeight()); |
| } |
| |
| /** |
| * Returns the sorted output sizes according to the use case config. |
| * |
| * <p>If ResolutionSelector is specified in the use case config, the output sizes will be |
| * sorted according to the ResolutionSelector setting and logic. Otherwise, the output sizes |
| * will be sorted according to the legacy resolution API settings and logic. |
| */ |
| @NonNull |
| List<Size> getSortedSupportedOutputSizes(@NonNull UseCaseConfig<?> useCaseConfig) { |
| ImageOutputConfig imageOutputConfig = (ImageOutputConfig) useCaseConfig; |
| List<Size> customOrderedResolutions = imageOutputConfig.getCustomOrderedResolutions(null); |
| |
| // Directly returns the custom ordered resolutions list if it is set. |
| if (customOrderedResolutions != null) { |
| return customOrderedResolutions; |
| } |
| |
| ResolutionSelector resolutionSelector = imageOutputConfig.getResolutionSelector(null); |
| List<Pair<Integer, Size[]>> customResolutions = |
| imageOutputConfig.getSupportedResolutions(null); |
| List<Size> candidateSizes = getResolutionCandidateList(customResolutions, |
| useCaseConfig.getInputFormat()); |
| |
| if (resolutionSelector == null) { |
| return mSupportedOutputSizesSorterLegacy.sortSupportedOutputSizes( |
| candidateSizes, useCaseConfig); |
| } else { |
| Size maxResolution = ((ImageOutputConfig) useCaseConfig).getMaxResolution(null); |
| int targetRotation = imageOutputConfig.getTargetRotation(Surface.ROTATION_0); |
| // Applies the high resolution settings onto the resolution candidate list. |
| if (!useCaseConfig.isHigResolutionDisabled(false)) { |
| candidateSizes = applyHighResolutionSettings(candidateSizes, |
| resolutionSelector, useCaseConfig.getInputFormat()); |
| } |
| return sortSupportedOutputSizesByResolutionSelector( |
| imageOutputConfig.getResolutionSelector(), |
| candidateSizes, |
| maxResolution, |
| targetRotation, |
| mFullFovRatio, |
| mSensorOrientation, |
| mLensFacing); |
| } |
| } |
| |
| @Nullable |
| private List<Size> getSizeListByFormat( |
| @Nullable List<Pair<Integer, Size[]>> resolutionsPairList, |
| int imageFormat) { |
| Size[] outputSizes = null; |
| |
| if (resolutionsPairList != null) { |
| for (Pair<Integer, Size[]> formatResolutionPair : resolutionsPairList) { |
| if (formatResolutionPair.first == imageFormat) { |
| outputSizes = formatResolutionPair.second; |
| break; |
| } |
| } |
| } |
| return outputSizes == null ? null : Arrays.asList(outputSizes); |
| } |
| |
| /** |
| * Sorts the resolution candidate list according to the ResolutionSelector API logic. |
| * |
| * <ol> |
| * <li>Applies the aspect ratio strategy |
| * <ul> |
| * <li>Applies the aspect ratio strategy fallback rule |
| * </ul> |
| * <li>Applies the resolution strategy |
| * <ul> |
| * <li>Applies the resolution strategy fallback rule |
| * </ul> |
| * <li>Applies the resolution filter |
| * </ol> |
| * @param resolutionSelector the ResolutionSelector used to sort the candidate |
| * sizes. |
| * @param candidateSizes the candidate sizes after the high resolution processing, which |
| * will be sorted by the rule of ResolutionSelector. |
| * @param maxResolution the max resolutions which sizes larger than it will be removed |
| * from candidate sizes. |
| * @param targetRotation the target rotation to calculate the rotation degrees to the |
| * {@link ResolutionFilter}. |
| * @param fullFovRatio the full FOV's aspect ratio. |
| * @param sensorOrientation the sensor orientation of the current camera. |
| * @param lensFacing the lens facing of the current camera |
| * @return a size list which has been filtered and sorted by the specified resolution |
| * selector settings. |
| * @throws IllegalArgumentException if the specified resolution filter returns any size which |
| * is not included in the provided supported size list. |
| */ |
| @NonNull |
| public static List<Size> sortSupportedOutputSizesByResolutionSelector( |
| @NonNull ResolutionSelector resolutionSelector, |
| @NonNull List<Size> candidateSizes, |
| @Nullable Size maxResolution, |
| int targetRotation, |
| @NonNull Rational fullFovRatio, |
| int sensorOrientation, |
| int lensFacing) { |
| |
| // Applies the aspect ratio strategy onto the resolution candidate list. |
| LinkedHashMap<Rational, List<Size>> aspectRatioSizeListMap = |
| applyAspectRatioStrategy(candidateSizes, |
| resolutionSelector.getAspectRatioStrategy(), fullFovRatio); |
| |
| // Applies the max resolution setting |
| if (maxResolution != null) { |
| applyMaxResolutionRestriction(aspectRatioSizeListMap, maxResolution); |
| } |
| |
| // Applies the resolution strategy onto the resolution candidate list. |
| applyResolutionStrategy(aspectRatioSizeListMap, resolutionSelector.getResolutionStrategy()); |
| |
| // Collects all sizes from the sorted aspect ratio size groups into the final sorted list. |
| List<Size> resultList = new ArrayList<>(); |
| for (List<Size> sortedSizes : aspectRatioSizeListMap.values()) { |
| for (Size size : sortedSizes) { |
| // A size may exist in multiple groups in mod16 condition. Keep only one in |
| // the final list. |
| if (!resultList.contains(size)) { |
| resultList.add(size); |
| } |
| } |
| } |
| |
| // Applies the resolution filter onto the resolution candidate list. |
| return applyResolutionFilter(resultList, resolutionSelector.getResolutionFilter(), |
| targetRotation, sensorOrientation, lensFacing); |
| } |
| |
| /** |
| * Returns the normal supported output sizes. |
| * |
| * <p>When using camera-camera2 implementation, the output sizes are retrieved via |
| * StreamConfigurationMap#getOutputSizes(). |
| * |
| * @return the resolution candidate list sorted in descending order. |
| */ |
| @NonNull |
| private List<Size> getResolutionCandidateList( |
| @Nullable List<Pair<Integer, Size[]>> customResolutions, int imageFormat) { |
| // Tries to get the custom supported resolutions list if it is set |
| List<Size> resolutionCandidateList = getSizeListByFormat(customResolutions, imageFormat); |
| |
| // Tries to get the supported output sizes from the CameraInfoInternal if both custom |
| // ordered and supported resolutions lists are not set. |
| if (resolutionCandidateList == null) { |
| resolutionCandidateList = mCameraInfoInternal.getSupportedResolutions(imageFormat); |
| } |
| |
| // CameraInfoInternal.getSupportedResolutions is not guaranteed to return a modifiable list |
| // needed by Collections.sort(), so it is converted to a modifiable list here |
| resolutionCandidateList = new ArrayList<>(resolutionCandidateList); |
| |
| Collections.sort(resolutionCandidateList, new CompareSizesByArea(true)); |
| |
| if (resolutionCandidateList.isEmpty()) { |
| Logger.w(TAG, "The retrieved supported resolutions from camera info internal is empty" |
| + ". Format is " + imageFormat + "."); |
| } |
| |
| return resolutionCandidateList; |
| } |
| |
| /** |
| * Appends the high resolution supported output sizes according to the high resolution settings. |
| * |
| * <p>When using camera-camera2 implementation, the output sizes are retrieved via |
| * StreamConfigurationMap#getHighResolutionOutputSizes(). |
| * |
| * @param resolutionCandidateList the supported size list which contains only normal output |
| * sizes. |
| * @param resolutionSelector the specified resolution selector. |
| * @param imageFormat the desired image format for the target use case. |
| * @return the resolution candidate list including the high resolution output sizes sorted in |
| * descending order. |
| */ |
| @NonNull |
| private List<Size> applyHighResolutionSettings(@NonNull List<Size> resolutionCandidateList, |
| @NonNull ResolutionSelector resolutionSelector, int imageFormat) { |
| // Appends high resolution output sizes if high resolution is enabled by ResolutionSelector |
| if (resolutionSelector.getAllowedResolutionMode() |
| == ResolutionSelector.PREFER_HIGHER_RESOLUTION_OVER_CAPTURE_RATE) { |
| List<Size> allSizesList = new ArrayList<>(); |
| allSizesList.addAll(resolutionCandidateList); |
| allSizesList.addAll(mCameraInfoInternal.getSupportedHighResolutions(imageFormat)); |
| Collections.sort(allSizesList, new CompareSizesByArea(true)); |
| return allSizesList; |
| } |
| |
| return resolutionCandidateList; |
| } |
| |
| /** |
| * Applies the aspect ratio strategy onto the input resolution candidate list. |
| * |
| * @param resolutionCandidateList the supported sizes list which has been sorted in |
| * descending order. |
| * @param aspectRatioStrategy the specified aspect ratio strategy. |
| * @return an aspect ratio to size list linked hash map which the aspect ratio fallback rule |
| * is applied and is sorted against the preferred aspect ratio. |
| */ |
| @NonNull |
| private static LinkedHashMap<Rational, List<Size>> applyAspectRatioStrategy( |
| @NonNull List<Size> resolutionCandidateList, |
| @NonNull AspectRatioStrategy aspectRatioStrategy, |
| Rational fullFovRatio) { |
| // Group output sizes by aspect ratio. |
| Map<Rational, List<Size>> aspectRatioSizeListMap = |
| groupSizesByAspectRatio(resolutionCandidateList); |
| |
| // Applies the aspect ratio fallback rule |
| return applyAspectRatioStrategyFallbackRule( |
| aspectRatioSizeListMap, aspectRatioStrategy, fullFovRatio); |
| } |
| |
| /** |
| * Applies the aspect ratio strategy fallback rule to the aspect ratio to size list map. |
| * |
| * @param sizeGroupsMap the aspect ratio to size list map. The size list should have been |
| * sorted in descending order. |
| * @param aspectRatioStrategy the specified aspect ratio strategy. |
| * @return an aspect ratio to size list linked hash map which the aspect ratio fallback rule |
| * is applied and is sorted against the preferred aspect ratio. |
| */ |
| private static LinkedHashMap<Rational, List<Size>> applyAspectRatioStrategyFallbackRule( |
| @NonNull Map<Rational, List<Size>> sizeGroupsMap, |
| @NonNull AspectRatioStrategy aspectRatioStrategy, |
| Rational fullFovRatio) { |
| // Determines the sensor resolution orientation info by the full FOV ratio. |
| boolean isSensorLandscapeResolution = fullFovRatio != null ? fullFovRatio.getNumerator() |
| >= fullFovRatio.getDenominator() : true; |
| Rational aspectRatio = getTargetAspectRatioRationalValue( |
| aspectRatioStrategy.getPreferredAspectRatio(), isSensorLandscapeResolution); |
| |
| // Remove items of all other aspect ratios if the fallback rule is AspectRatioStrategy |
| // .FALLBACK_RULE_NONE |
| if (aspectRatioStrategy.getFallbackRule() == AspectRatioStrategy.FALLBACK_RULE_NONE) { |
| Rational preferredAspectRatio = getTargetAspectRatioRationalValue( |
| aspectRatioStrategy.getPreferredAspectRatio(), isSensorLandscapeResolution); |
| for (Rational ratio : new ArrayList<>(sizeGroupsMap.keySet())) { |
| if (!ratio.equals(preferredAspectRatio)) { |
| sizeGroupsMap.remove(ratio); |
| } |
| } |
| } |
| |
| // Sorts the aspect ratio key set by the preferred aspect ratio. |
| List<Rational> aspectRatios = new ArrayList<>(sizeGroupsMap.keySet()); |
| Collections.sort(aspectRatios, |
| new AspectRatioUtil.CompareAspectRatiosByMappingAreaInFullFovAspectRatioSpace( |
| aspectRatio, fullFovRatio)); |
| |
| // Stores the size groups into LinkedHashMap to keep the order |
| LinkedHashMap<Rational, List<Size>> sortedAspectRatioSizeListMap = new LinkedHashMap<>(); |
| for (Rational ratio : aspectRatios) { |
| sortedAspectRatioSizeListMap.put(ratio, sizeGroupsMap.get(ratio)); |
| } |
| |
| return sortedAspectRatioSizeListMap; |
| } |
| |
| /** |
| * Applies the resolution strategy onto the aspect ratio to size list linked hash map. |
| * |
| * <p>The resolution fallback rule is applied to filter out and sort the sizes in the |
| * underlying size list. |
| * |
| * @param sortedAspectRatioSizeListMap the aspect ratio to size list linked hash map. The |
| * entries order should not be changed. |
| * @param resolutionStrategy the resolution strategy to sort the candidate |
| * resolutions. |
| */ |
| private static void applyResolutionStrategy( |
| @NonNull LinkedHashMap<Rational, List<Size>> sortedAspectRatioSizeListMap, |
| @Nullable ResolutionStrategy resolutionStrategy) { |
| if (resolutionStrategy == null) { |
| return; |
| } |
| |
| // Applies the resolution strategy with the specified fallback rule |
| for (Rational key : sortedAspectRatioSizeListMap.keySet()) { |
| applyResolutionStrategyFallbackRule(sortedAspectRatioSizeListMap.get(key), |
| resolutionStrategy); |
| } |
| } |
| |
| /** |
| * Applies the resolution strategy fallback rule to the size list. |
| * |
| * @param supportedSizesList the supported sizes list which has been sorted in descending order. |
| * @param resolutionStrategy the resolution strategy to sort the candidate resolutions. |
| */ |
| private static void applyResolutionStrategyFallbackRule( |
| @NonNull List<Size> supportedSizesList, |
| @NonNull ResolutionStrategy resolutionStrategy) { |
| if (supportedSizesList.isEmpty()) { |
| return; |
| } |
| Integer fallbackRule = resolutionStrategy.getFallbackRule(); |
| |
| if (resolutionStrategy.equals(ResolutionStrategy.HIGHEST_AVAILABLE_STRATEGY)) { |
| // Do nothing for HIGHEST_AVAILABLE_STRATEGY case. |
| return; |
| } |
| |
| Size boundSize = resolutionStrategy.getBoundSize(); |
| |
| switch (fallbackRule) { |
| case ResolutionStrategy.FALLBACK_RULE_NONE: |
| sortSupportedSizesByFallbackRuleNone(supportedSizesList, boundSize); |
| break; |
| case ResolutionStrategy.FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER: |
| sortSupportedSizesByFallbackRuleClosestHigherThenLower(supportedSizesList, |
| boundSize, true); |
| break; |
| case ResolutionStrategy.FALLBACK_RULE_CLOSEST_HIGHER: |
| sortSupportedSizesByFallbackRuleClosestHigherThenLower(supportedSizesList, |
| boundSize, false); |
| break; |
| case ResolutionStrategy.FALLBACK_RULE_CLOSEST_LOWER_THEN_HIGHER: |
| sortSupportedSizesByFallbackRuleClosestLowerThenHigher(supportedSizesList, |
| boundSize, true); |
| break; |
| case ResolutionStrategy.FALLBACK_RULE_CLOSEST_LOWER: |
| sortSupportedSizesByFallbackRuleClosestLowerThenHigher(supportedSizesList, |
| boundSize, false); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /** |
| * Applies the max resolution restriction. |
| * |
| * <p>Filters out the output sizes that exceed the max resolution in area size. |
| * |
| * @param sortedAspectRatioSizeListMap the aspect ratio to size list linked hash map. The |
| * entries order should not be changed. |
| * @param maxResolution the max resolution size. |
| */ |
| private static void applyMaxResolutionRestriction( |
| @NonNull LinkedHashMap<Rational, List<Size>> sortedAspectRatioSizeListMap, |
| @NonNull Size maxResolution) { |
| int maxResolutionAreaSize = SizeUtil.getArea(maxResolution); |
| for (Rational key : sortedAspectRatioSizeListMap.keySet()) { |
| List<Size> supportedSizesList = sortedAspectRatioSizeListMap.get(key); |
| List<Size> filteredResultList = new ArrayList<>(); |
| for (Size size : supportedSizesList) { |
| if (SizeUtil.getArea(size) <= maxResolutionAreaSize) { |
| filteredResultList.add(size); |
| } |
| } |
| supportedSizesList.clear(); |
| supportedSizesList.addAll(filteredResultList); |
| } |
| } |
| |
| /** |
| * Applies the resolution filtered to the sorted output size list. |
| * |
| * @param sizeList the supported size list which has been filtered and sorted by the |
| * specified aspect ratio, resolution strategies. |
| * @param resolutionFilter the specified resolution filter. |
| * @param targetRotation the use case target rotation info |
| * @return the result size list applied the specified resolution filter. |
| * @throws IllegalArgumentException if the specified resolution filter returns any size which |
| * is not included in the provided supported size list. |
| */ |
| @NonNull |
| private static List<Size> applyResolutionFilter(@NonNull List<Size> sizeList, |
| @Nullable ResolutionFilter resolutionFilter, |
| @ImageOutputConfig.RotationValue int targetRotation, |
| int sensorOrientation, |
| int lensFacing) { |
| if (resolutionFilter == null) { |
| return sizeList; |
| } |
| |
| // Invokes ResolutionFilter#filter() to filter/sort and return the result if it is |
| // specified. |
| int destRotationDegrees = CameraOrientationUtil.surfaceRotationToDegrees( |
| targetRotation); |
| int rotationDegrees = |
| CameraOrientationUtil.getRelativeImageRotation(destRotationDegrees, |
| sensorOrientation, |
| lensFacing == CameraSelector.LENS_FACING_BACK); |
| List<Size> filteredResultList = resolutionFilter.filter(new ArrayList<>(sizeList), |
| rotationDegrees); |
| if (sizeList.containsAll(filteredResultList)) { |
| return filteredResultList; |
| } else { |
| throw new IllegalArgumentException("The returned sizes list of the resolution " |
| + "filter must be a subset of the provided sizes list."); |
| } |
| } |
| |
| /** |
| * Sorts the size list for {@link ResolutionStrategy#FALLBACK_RULE_NONE}. |
| * |
| * @param supportedSizesList the supported sizes list which has been sorted in descending order. |
| * @param boundSize the resolution strategy bound size. |
| */ |
| private static void sortSupportedSizesByFallbackRuleNone( |
| @NonNull List<Size> supportedSizesList, @NonNull Size boundSize) { |
| boolean containsBoundSize = supportedSizesList.contains(boundSize); |
| supportedSizesList.clear(); |
| if (containsBoundSize) { |
| supportedSizesList.add(boundSize); |
| } |
| } |
| |
| /** |
| * Sorts the size list for {@link ResolutionStrategy#FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER} |
| * or {@link ResolutionStrategy#FALLBACK_RULE_CLOSEST_HIGHER}. |
| * |
| * @param supportedSizesList the supported sizes list which has been sorted in descending order. |
| * @param boundSize the resolution strategy bound size. |
| * @param keepLowerSizes keeps the sizes lower than the bound size in the result list if |
| * this is {@code true}. |
| */ |
| static void sortSupportedSizesByFallbackRuleClosestHigherThenLower( |
| @NonNull List<Size> supportedSizesList, @NonNull Size boundSize, |
| boolean keepLowerSizes) { |
| List<Size> lowerSizes = new ArrayList<>(); |
| |
| for (int i = supportedSizesList.size() - 1; i >= 0; i--) { |
| Size outputSize = supportedSizesList.get(i); |
| if (outputSize.getWidth() < boundSize.getWidth() |
| || outputSize.getHeight() < boundSize.getHeight()) { |
| // The supportedSizesList is in descending order. Checking and put the |
| // bounding-below size at position 0 so that the largest smaller resolution |
| // will be put in the first position finally. |
| lowerSizes.add(0, outputSize); |
| } else { |
| break; |
| } |
| } |
| // Removes the lower sizes from the list |
| supportedSizesList.removeAll(lowerSizes); |
| // Reverses the list so that the smallest larger resolution will be put in the first |
| // position. |
| Collections.reverse(supportedSizesList); |
| if (keepLowerSizes) { |
| // Appends the lower sizes to the tail |
| supportedSizesList.addAll(lowerSizes); |
| } |
| } |
| |
| /** |
| * Sorts the size list for {@link ResolutionStrategy#FALLBACK_RULE_CLOSEST_LOWER_THEN_HIGHER} |
| * or {@link ResolutionStrategy#FALLBACK_RULE_CLOSEST_LOWER}. |
| * |
| * @param supportedSizesList the supported sizes list which has been sorted in descending order. |
| * @param boundSize the resolution strategy bound size. |
| * @param keepHigherSizes keeps the sizes higher than the bound size in the result list if |
| * this is {@code true}. |
| */ |
| private static void sortSupportedSizesByFallbackRuleClosestLowerThenHigher( |
| @NonNull List<Size> supportedSizesList, @NonNull Size boundSize, |
| boolean keepHigherSizes) { |
| List<Size> higherSizes = new ArrayList<>(); |
| |
| for (int i = 0; i < supportedSizesList.size(); i++) { |
| Size outputSize = supportedSizesList.get(i); |
| if (outputSize.getWidth() > boundSize.getWidth() |
| || outputSize.getHeight() > boundSize.getHeight()) { |
| // The supportedSizesList is in descending order. Checking and put the |
| // bounding-above size at position 0 so that the smallest larger resolution |
| // will be put in the first position finally. |
| higherSizes.add(0, outputSize); |
| } else { |
| // Breaks the for-loop to keep the equal-to or lower sizes in the list. |
| break; |
| } |
| } |
| // Removes the higher sizes from the list |
| supportedSizesList.removeAll(higherSizes); |
| if (keepHigherSizes) { |
| // Appends the higher sizes to the tail |
| supportedSizesList.addAll(higherSizes); |
| } |
| } |
| |
| /** |
| * Returns the target aspect ratio rational value according to the ResolutionSelector settings. |
| */ |
| @Nullable |
| static Rational getTargetAspectRatioRationalValue(@AspectRatio.Ratio int aspectRatio, |
| boolean isSensorLandscapeResolution) { |
| Rational outputRatio = null; |
| |
| switch (aspectRatio) { |
| case AspectRatio.RATIO_4_3: |
| outputRatio = isSensorLandscapeResolution ? ASPECT_RATIO_4_3 |
| : ASPECT_RATIO_3_4; |
| break; |
| case AspectRatio.RATIO_16_9: |
| outputRatio = isSensorLandscapeResolution ? ASPECT_RATIO_16_9 |
| : ASPECT_RATIO_9_16; |
| break; |
| case AspectRatio.RATIO_DEFAULT: |
| break; |
| default: |
| Logger.e(TAG, "Undefined target aspect ratio: " + aspectRatio); |
| } |
| |
| return outputRatio; |
| } |
| |
| /** |
| * Returns the grouping aspect ratio keys of the input resolution list. |
| * |
| * <p>Some sizes might be mod16 case. When grouping, those sizes will be grouped into an |
| * existing aspect ratio group if the aspect ratio can match by the mod16 rule. |
| */ |
| @NonNull |
| static List<Rational> getResolutionListGroupingAspectRatioKeys( |
| @NonNull List<Size> resolutionCandidateList) { |
| List<Rational> aspectRatios = new ArrayList<>(); |
| |
| // Adds the default 4:3 and 16:9 items first to avoid their mod16 sizes to create |
| // additional items. |
| aspectRatios.add(ASPECT_RATIO_4_3); |
| aspectRatios.add(ASPECT_RATIO_16_9); |
| |
| // Tries to find the aspect ratio which the target size belongs to. |
| for (Size size : resolutionCandidateList) { |
| Rational newRatio = new Rational(size.getWidth(), size.getHeight()); |
| boolean aspectRatioFound = aspectRatios.contains(newRatio); |
| |
| // The checking size might be a mod16 size which can be mapped to an existing aspect |
| // ratio group. |
| if (!aspectRatioFound) { |
| boolean hasMatchingAspectRatio = false; |
| for (Rational aspectRatio : aspectRatios) { |
| if (hasMatchingAspectRatio(size, aspectRatio)) { |
| hasMatchingAspectRatio = true; |
| break; |
| } |
| } |
| if (!hasMatchingAspectRatio) { |
| aspectRatios.add(newRatio); |
| } |
| } |
| } |
| |
| return aspectRatios; |
| } |
| |
| /** |
| * Groups the input sizes into an aspect ratio to size list map. |
| */ |
| static Map<Rational, List<Size>> groupSizesByAspectRatio(@NonNull List<Size> sizes) { |
| Map<Rational, List<Size>> aspectRatioSizeListMap = new HashMap<>(); |
| |
| List<Rational> aspectRatioKeys = getResolutionListGroupingAspectRatioKeys(sizes); |
| |
| for (Rational aspectRatio : aspectRatioKeys) { |
| aspectRatioSizeListMap.put(aspectRatio, new ArrayList<>()); |
| } |
| |
| for (Size outputSize : sizes) { |
| for (Rational key : aspectRatioSizeListMap.keySet()) { |
| // Put the size into all groups that is matched in mod16 condition since a size |
| // may match multiple aspect ratio in mod16 algorithm. |
| if (hasMatchingAspectRatio(outputSize, key)) { |
| aspectRatioSizeListMap.get(key).add(outputSize); |
| } |
| } |
| } |
| |
| return aspectRatioSizeListMap; |
| } |
| } |