blob: deca9878f28025b78517757bf990d376d9f77909 [file] [log] [blame]
/*
* Copyright (C) 2021 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 com.android.server.display;
import java.util.Arrays;
import java.util.Comparator;
/**
* Class which can compute the logical density for a display resolution. It holds a collection
* of pre-configured densities, which are used for look-up and interpolation.
*/
public class DensityMapping {
// Instead of resolutions we store the squared diagonal size. Diagonals make the map
// keys invariant to rotations and are useful for interpolation because they're scalars.
// Squared diagonals have the same properties as diagonals (the square function is monotonic)
// but also allow us to use integer types and avoid floating point arithmetics.
private final Entry[] mSortedDensityMappingEntries;
/**
* Creates a density mapping. The newly created object takes ownership of the passed array.
*/
static DensityMapping createByOwning(Entry[] densityMappingEntries) {
return new DensityMapping(densityMappingEntries);
}
private DensityMapping(Entry[] densityMappingEntries) {
Arrays.sort(densityMappingEntries, Comparator.comparingInt(
entry -> entry.squaredDiagonal));
mSortedDensityMappingEntries = densityMappingEntries;
verifyDensityMapping(mSortedDensityMappingEntries);
}
/**
* Returns the logical density for the given resolution.
*
* If the resolution matches one of the entries in the mapping, the corresponding density is
* returned. Otherwise the return value is interpolated using the closest entries in the map.
*/
public int getDensityForResolution(int width, int height) {
int squaredDiagonal = width * width + height * height;
// Search for two pre-configured entries "left" and "right" with the following criteria
// * left <= squaredDiagonal
// * squaredDiagonal - left is minimal
// * right > squaredDiagonal
// * right - squaredDiagonal is minimal
Entry left = Entry.ZEROES;
Entry right = null;
for (Entry entry : mSortedDensityMappingEntries) {
if (entry.squaredDiagonal <= squaredDiagonal) {
left = entry;
} else {
right = entry;
break;
}
}
// Check if we found an exact match.
if (left.squaredDiagonal == squaredDiagonal) {
return left.density;
}
// If no configured resolution is higher than the specified resolution, interpolate
// between (0,0) and (maxConfiguredDiagonal, maxConfiguredDensity).
if (right == null) {
right = left; // largest entry in the sorted array
left = Entry.ZEROES;
}
double leftDiagonal = Math.sqrt(left.squaredDiagonal);
double rightDiagonal = Math.sqrt(right.squaredDiagonal);
double diagonal = Math.sqrt(squaredDiagonal);
return (int) Math.round((diagonal - leftDiagonal) * (right.density - left.density)
/ (rightDiagonal - leftDiagonal) + left.density);
}
private static void verifyDensityMapping(Entry[] sortedEntries) {
for (int i = 1; i < sortedEntries.length; i++) {
Entry prev = sortedEntries[i - 1];
Entry curr = sortedEntries[i];
if (prev.squaredDiagonal == curr.squaredDiagonal) {
// This will most often happen because there are two entries with the same
// resolution (AxB and AxB) or rotated resolution (AxB and BxA), but it can also
// happen in the very rare cases when two different resolutions happen to have
// the same diagonal (e.g. 100x700 and 500x500).
throw new IllegalStateException("Found two entries in the density mapping with"
+ " the same diagonal: " + prev + ", " + curr);
} else if (prev.density > curr.density) {
throw new IllegalStateException("Found two entries in the density mapping with"
+ " increasing diagonal but decreasing density: " + prev + ", " + curr);
}
}
}
@Override
public String toString() {
return "DensityMapping{"
+ "mDensityMappingEntries=" + Arrays.toString(mSortedDensityMappingEntries)
+ '}';
}
static class Entry {
public static final Entry ZEROES = new Entry(0, 0, 0);
public final int squaredDiagonal;
public final int density;
Entry(int width, int height, int density) {
this.squaredDiagonal = width * width + height * height;
this.density = density;
}
@Override
public String toString() {
return "DensityMappingEntry{"
+ "squaredDiagonal=" + squaredDiagonal
+ ", density=" + density + '}';
}
}
}