Skip to content

Commit 60eabaf

Browse files
author
Yevhen Babiichuk (DustDFG)
committed
Convert newpipe/util/image/ImageStrategy to kotlin
1 parent 8ae5a55 commit 60eabaf

2 files changed

Lines changed: 182 additions & 195 deletions

File tree

app/src/main/java/org/schabi/newpipe/util/image/ImageStrategy.java

Lines changed: 0 additions & 195 deletions
This file was deleted.
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
package org.schabi.newpipe.util.image
2+
3+
import org.schabi.newpipe.extractor.Image
4+
import org.schabi.newpipe.extractor.Image.ResolutionLevel
5+
import kotlin.math.abs
6+
7+
object ImageStrategy {
8+
// when preferredImageQuality is LOW or MEDIUM, images are sorted by how close their preferred
9+
// image quality is to these values (H stands for "Height")
10+
private const val BEST_LOW_H = 75
11+
private const val BEST_MEDIUM_H = 250
12+
13+
private var preferredImageQuality = PreferredImageQuality.MEDIUM
14+
15+
@JvmStatic
16+
fun setPreferredImageQuality(preferredImageQuality: PreferredImageQuality) {
17+
ImageStrategy.preferredImageQuality = preferredImageQuality
18+
}
19+
20+
@JvmStatic
21+
fun shouldLoadImages(): Boolean {
22+
return preferredImageQuality != PreferredImageQuality.NONE
23+
}
24+
25+
@JvmStatic
26+
fun estimatePixelCount(image: Image, widthOverHeight: Double): Double {
27+
if (image.height == Image.HEIGHT_UNKNOWN) {
28+
if (image.width == Image.WIDTH_UNKNOWN) {
29+
// images whose size is completely unknown will be in their own subgroups, so
30+
// any one of them will do, hence returning the same value for all of them
31+
return 0.0
32+
} else {
33+
return image.width * image.width / widthOverHeight
34+
}
35+
} else if (image.width == Image.WIDTH_UNKNOWN) {
36+
return image.height * image.height * widthOverHeight
37+
} else {
38+
return (image.height * image.width).toDouble()
39+
}
40+
}
41+
42+
/**
43+
* [choosePreferredImage] contains the description for this function's logic.
44+
*
45+
* @param images the images from which to choose
46+
* @param nonNoneQuality the preferred quality (must NOT be [PreferredImageQuality.NONE])
47+
* @return the chosen preferred image, or `null` if the list is empty
48+
* @see [choosePreferredImage]
49+
*/
50+
@JvmStatic
51+
fun choosePreferredImage(images: List<Image>, nonNoneQuality: PreferredImageQuality): String? {
52+
// this will be used to estimate the pixel count for images where only one of height or
53+
// width are known
54+
val widthOverHeight = images
55+
.filter { image ->
56+
image.height != Image.HEIGHT_UNKNOWN && image.width != Image.WIDTH_UNKNOWN
57+
}
58+
.map { image -> (image.width.toDouble()) / image.height }
59+
.elementAtOrNull(0) ?: 1.0
60+
61+
val preferredLevel = nonNoneQuality.toResolutionLevel()
62+
// TODO: rewrite using kotlin collections API `groupBy` will be handy
63+
val initialComparator =
64+
Comparator // the first step splits the images into groups of resolution levels
65+
.comparingInt { i: Image ->
66+
if (i.estimatedResolutionLevel == ResolutionLevel.UNKNOWN) {
67+
return@comparingInt 3 // avoid unknowns as much as possible
68+
} else if (i.estimatedResolutionLevel == preferredLevel) {
69+
return@comparingInt 0 // prefer a matching resolution level
70+
} else if (i.estimatedResolutionLevel == ResolutionLevel.MEDIUM) {
71+
return@comparingInt 1 // the preferredLevel is only 1 "step" away (either HIGH or LOW)
72+
} else {
73+
return@comparingInt 2 // the preferredLevel is the furthest away possible (2 "steps")
74+
}
75+
}
76+
// then each level's group is further split into two subgroups, one with known image
77+
// size (which is also the preferred subgroup) and the other without
78+
.thenComparing { image -> image.height == Image.HEIGHT_UNKNOWN && image.width == Image.WIDTH_UNKNOWN }
79+
80+
// The third step chooses, within each subgroup with known image size, the best image based
81+
// on how close its size is to BEST_LOW_H or BEST_MEDIUM_H (with proper units). Subgroups
82+
// without known image size will be left untouched since estimatePixelCount always returns
83+
// the same number for those.
84+
val finalComparator = when (nonNoneQuality) {
85+
PreferredImageQuality.NONE -> initialComparator
86+
PreferredImageQuality.LOW -> initialComparator.thenComparingDouble { image ->
87+
val pixelCount = estimatePixelCount(image, widthOverHeight)
88+
abs(pixelCount - BEST_LOW_H * BEST_LOW_H * widthOverHeight)
89+
}
90+
91+
PreferredImageQuality.MEDIUM -> initialComparator.thenComparingDouble { image ->
92+
val pixelCount = estimatePixelCount(image, widthOverHeight)
93+
abs(pixelCount - BEST_MEDIUM_H * BEST_MEDIUM_H * widthOverHeight)
94+
}
95+
96+
PreferredImageQuality.HIGH -> initialComparator.thenComparingDouble { image ->
97+
// this is reversed with a - so that the highest resolution is chosen
98+
-estimatePixelCount(image, widthOverHeight)
99+
}
100+
}
101+
102+
return images.stream() // using "min" basically means "take the first group, then take the first subgroup,
103+
// then choose the best image, while ignoring all other groups and subgroups"
104+
.min(finalComparator)
105+
.map(Image::getUrl)
106+
.orElse(null)
107+
}
108+
109+
/**
110+
* Chooses an image amongst the provided list based on the user preference previously set with
111+
* [setPreferredImageQuality]. `null` will be returned in
112+
* case the list is empty or the user preference is to not show images.
113+
* <br>
114+
* These properties will be preferred, from most to least important:
115+
*
116+
* 1. The image's [Image.estimatedResolutionLevel] is not unknown and is close to [preferredImageQuality]
117+
* 2. At least one of the image's width or height are known
118+
* 3. The highest resolution image is finally chosen if the user's preference is
119+
* [PreferredImageQuality.HIGH], otherwise the chosen image is the one that has the height
120+
* closest to [BEST_LOW_H] or [BEST_MEDIUM_H]
121+
*
122+
* <br>
123+
* Use [imageListToDbUrl] if the URL is going to be saved to the database, to avoid
124+
* saving nothing in case at the moment of saving the user preference is to not show images.
125+
*
126+
* @param images the images from which to choose
127+
* @return the chosen preferred image, or `null` if the list is empty or the user disabled
128+
* images
129+
* @see [imageListToDbUrl]
130+
*/
131+
@JvmStatic
132+
fun choosePreferredImage(images: List<Image>): String? {
133+
if (preferredImageQuality == PreferredImageQuality.NONE) {
134+
return null // do not load images
135+
}
136+
137+
return choosePreferredImage(images, preferredImageQuality)
138+
}
139+
140+
/**
141+
* Like [choosePreferredImage], except that if [preferredImageQuality] is
142+
* [PreferredImageQuality.NONE] an image will be chosen anyway (with preferred quality
143+
* [PreferredImageQuality.MEDIUM].
144+
* <br></br>
145+
* To go back to a list of images (obviously with just the one chosen image) from a URL saved in
146+
* the database use [dbUrlToImageList].
147+
*
148+
* @param images the images from which to choose
149+
* @return the chosen preferred image, or `null` if the list is empty
150+
* @see [choosePreferredImage]
151+
* @see [dbUrlToImageList]
152+
*/
153+
@JvmStatic
154+
fun imageListToDbUrl(images: List<Image>): String? {
155+
val quality = when (preferredImageQuality) {
156+
PreferredImageQuality.NONE -> PreferredImageQuality.MEDIUM
157+
else -> preferredImageQuality
158+
}
159+
160+
return choosePreferredImage(images, quality)
161+
}
162+
163+
/**
164+
* Wraps the URL (coming from the database) in a `List<Image>` so that it is usable
165+
* seamlessly in all of the places where the extractor would return a list of images, including
166+
* allowing to build info objects based on database objects.
167+
* <br></br>
168+
* To obtain a url to save to the database from a list of images use [imageListToDbUrl].
169+
*
170+
* @param url the URL to wrap coming from the database, or `null` to get an empty list
171+
* @return a list containing just one [Image] wrapping the provided URL, with unknown
172+
* image size fields, or an empty list if the URL is `null`
173+
* @see [imageListToDbUrl]
174+
*/
175+
@JvmStatic
176+
fun dbUrlToImageList(url: String?): List<Image> {
177+
return when (url) {
178+
null -> listOf()
179+
else -> listOf(Image(url, -1, -1, ResolutionLevel.UNKNOWN))
180+
}
181+
}
182+
}

0 commit comments

Comments
 (0)