Last active
November 17, 2020 12:06
-
-
Save jcayzac/fc8444e3df88fbe59e15a2f0c2d48a83 to your computer and use it in GitHub Desktop.
Image size fitting computations
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
data class RenderSize(val width: Int, val height: Int) { | |
companion object { | |
// 0x0 is an alias for "any" | |
val ANY = RenderSize(0, 0) | |
} | |
} | |
/** | |
* Scores an image resource based on the set of sizes it supports and on the | |
* size the caller wants to render it inside of. | |
* | |
* The result is a value between 0 and 1, inclusive. The higher the fitter. | |
* | |
* The function favors images that support the requested size exactly. Images | |
* that support "any" size come next. Images that only support sizes either | |
* immensely too large or immensely too small are filtered out. The remaining | |
* images get a score based on their difference in size and aspect ratio. | |
* | |
* The smallest images larger than the requested size are treated more | |
* favorably than the largest images smaller than it. Furthermore, a good | |
* aspect ratio is treated more importantly than the need to downscale, although | |
* the penalty might be less than the one incurred by a need to upscale. | |
* | |
* For other, more limited implementations, see: | |
* | |
* - [`ManifestIconSelector::FindBestMatchingIcon`](https://github.com/chromium/chromium/blob/master/third_party/blink/common/manifest/manifest_icon_selector.cc) | |
* in Blink. | |
* - [`GetCandidateIndexWithBestScore`](https://github.com/chromium/chromium/blob/master/components/favicon_base/select_favicon_frames.cc) | |
* in Chromium. | |
*/ | |
fun score(supportedSizes: Set<RenderSize>, requestedSize: RenderSize): Double { | |
// Give maximum score to exact size matches | |
if (supportedSizes.contains(requestedSize)) | |
return 1.0 | |
// An image supporting "any" size is a near-perfect match | |
if (supportedSizes.contains(RenderSize.ANY)) | |
return .99999999 | |
// Images with huge differences in scale generally aren't suitable. | |
// We reject anything smaller or larger than a up/downscale factor of 8. | |
val maxWidth: Int = requestedSize.width.shl(3) | |
val maxHeight: Int = requestedSize.height.shl(3) | |
val minWidth: Int = requestedSize.width.ushr(3) | |
val minHeight: Int = requestedSize.height.ushr(3) | |
// Compute some fixed values outside of the iteration below | |
val idealWidth: Double = requestedSize.width.toDouble() | |
val idealHeight: Double = requestedSize.height.toDouble() | |
val idealRatio: Double = idealWidth / idealHeight | |
return supportedSizes | |
.filter { (width: Int, height: Int) -> | |
// If the size is too large it should be rejected | |
width <= maxWidth && height <= maxHeight | |
} | |
.filter { (width: Int, height: Int) -> | |
// If the size is too small it should be rejected | |
width > minWidth && height > minHeight | |
} | |
.map { ( width: Int, height: Int ) -> | |
// Convert to double | |
Pair(width.toDouble(), height.toDouble()) | |
} | |
.maxOfOrNull { (width: Double, height: Double) -> | |
val widthDiff: Double = width - idealWidth | |
val heightDiff: Double = height - idealHeight | |
var score = 1.0 | |
// Bigger sizes get a penalty up to 25% per dimension | |
if (widthDiff > 0) score *= 1.0 - (widthDiff * 3) / (idealWidth * 28) | |
if (heightDiff > 0) score *= 1.0 - (heightDiff * 3) / (idealHeight * 28) | |
// Smaller sizes get a penalty of at least 25%, up to 75% per dimension | |
if (widthDiff < 0) score *= .25 - (widthDiff / idealWidth) * .5 | |
if (heightDiff < 0) score *= .25 - (heightDiff / idealHeight) * .5 | |
// Different aspect ratios get a penalty up to 50% | |
val ratio = width / height | |
score *= .5 * (min(ratio, idealRatio) / max(ratio, idealRatio) + 1.0) | |
score | |
} ?: /* rejected */ .0 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment