coil: use image size when creating mosaics
Use the image size provided by coil when creating our mosaics. This allows both better transformations and better-quality mosaics in the detail view itself.
This commit is contained in:
parent
22c54993ed
commit
c0b2dee3ff
2 changed files with 32 additions and 23 deletions
|
@ -12,6 +12,8 @@ import coil.fetch.DrawableResult
|
||||||
import coil.fetch.FetchResult
|
import coil.fetch.FetchResult
|
||||||
import coil.fetch.Fetcher
|
import coil.fetch.Fetcher
|
||||||
import coil.fetch.SourceResult
|
import coil.fetch.SourceResult
|
||||||
|
import coil.size.PixelSize
|
||||||
|
import coil.size.Size
|
||||||
import com.google.android.exoplayer2.MediaItem
|
import com.google.android.exoplayer2.MediaItem
|
||||||
import com.google.android.exoplayer2.MediaMetadata
|
import com.google.android.exoplayer2.MediaMetadata
|
||||||
import com.google.android.exoplayer2.MetadataRetriever
|
import com.google.android.exoplayer2.MetadataRetriever
|
||||||
|
@ -199,7 +201,7 @@ abstract class AuxioFetcher : Fetcher {
|
||||||
* Create a mosaic image from multiple streams of image data, Code adapted from Phonograph
|
* Create a mosaic image from multiple streams of image data, Code adapted from Phonograph
|
||||||
* https://github.com/kabouzeid/Phonograph
|
* https://github.com/kabouzeid/Phonograph
|
||||||
*/
|
*/
|
||||||
protected fun createMosaic(context: Context, streams: List<InputStream>): FetchResult? {
|
protected fun createMosaic(context: Context, streams: List<InputStream>, size: Size): FetchResult? {
|
||||||
if (streams.size < 4) {
|
if (streams.size < 4) {
|
||||||
return streams.getOrNull(0)?.let { stream ->
|
return streams.getOrNull(0)?.let { stream ->
|
||||||
return SourceResult(
|
return SourceResult(
|
||||||
|
@ -210,11 +212,17 @@ abstract class AuxioFetcher : Fetcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use a fixed 512x512 canvas for the mosaics. Preferably we would adapt this mosaic to
|
// Use whatever size coil gives us to create the mosaic. If there is no size, default
|
||||||
// target ImageView size, but Coil seems to start image loading before we can even get
|
// to a 512x512 mosaic.
|
||||||
// a width/height for the view, making that impractical.
|
val mosaicSize = when (size) {
|
||||||
|
is PixelSize -> size
|
||||||
|
else -> PixelSize(512, 512)
|
||||||
|
}
|
||||||
|
|
||||||
|
val increment = PixelSize(mosaicSize.width / 2, mosaicSize.height / 2)
|
||||||
|
|
||||||
val mosaicBitmap = Bitmap.createBitmap(
|
val mosaicBitmap = Bitmap.createBitmap(
|
||||||
MOSAIC_BITMAP_SIZE, MOSAIC_BITMAP_SIZE, Bitmap.Config.ARGB_8888
|
mosaicSize.width, mosaicSize.height, Bitmap.Config.ARGB_8888
|
||||||
)
|
)
|
||||||
|
|
||||||
val canvas = Canvas(mosaicBitmap)
|
val canvas = Canvas(mosaicBitmap)
|
||||||
|
@ -225,36 +233,34 @@ abstract class AuxioFetcher : Fetcher {
|
||||||
// For each stream, create a bitmap scaled to 1/4th of the mosaics combined size
|
// For each stream, create a bitmap scaled to 1/4th of the mosaics combined size
|
||||||
// and place it on a corner of the canvas.
|
// and place it on a corner of the canvas.
|
||||||
for (stream in streams) {
|
for (stream in streams) {
|
||||||
if (y == MOSAIC_BITMAP_SIZE) {
|
if (y == mosaicSize.height) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
val bitmap = Bitmap.createScaledBitmap(
|
val bitmap = Bitmap.createScaledBitmap(
|
||||||
BitmapFactory.decodeStream(stream),
|
BitmapFactory.decodeStream(stream),
|
||||||
MOSAIC_BITMAP_INCREMENT,
|
increment.width,
|
||||||
MOSAIC_BITMAP_INCREMENT,
|
increment.height,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
|
||||||
canvas.drawBitmap(bitmap, x.toFloat(), y.toFloat(), null)
|
canvas.drawBitmap(bitmap, x.toFloat(), y.toFloat(), null)
|
||||||
|
|
||||||
x += MOSAIC_BITMAP_INCREMENT
|
x += increment.width
|
||||||
|
|
||||||
if (x == MOSAIC_BITMAP_SIZE) {
|
if (x == mosaicSize.width) {
|
||||||
x = 0
|
x = 0
|
||||||
y += MOSAIC_BITMAP_INCREMENT
|
y += increment.height
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// It's way easier to map this into a drawable then try to serialize it into an
|
||||||
|
// BufferedSource. Just make sure we mark it as "sampled" so Coil doesn't try to
|
||||||
|
// load low-res mosaics into high-res ImageViews.
|
||||||
return DrawableResult(
|
return DrawableResult(
|
||||||
drawable = mosaicBitmap.toDrawable(context.resources),
|
drawable = mosaicBitmap.toDrawable(context.resources),
|
||||||
isSampled = false,
|
isSampled = true,
|
||||||
dataSource = DataSource.DISK
|
dataSource = DataSource.DISK
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val MOSAIC_BITMAP_SIZE = 512
|
|
||||||
private const val MOSAIC_BITMAP_INCREMENT = 256
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import coil.fetch.FetchResult
|
||||||
import coil.fetch.Fetcher
|
import coil.fetch.Fetcher
|
||||||
import coil.fetch.SourceResult
|
import coil.fetch.SourceResult
|
||||||
import coil.request.Options
|
import coil.request.Options
|
||||||
|
import coil.size.Size
|
||||||
import okio.buffer
|
import okio.buffer
|
||||||
import okio.source
|
import okio.source
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
|
@ -71,19 +72,20 @@ class AlbumArtFetcher private constructor(
|
||||||
*/
|
*/
|
||||||
class ArtistImageFetcher private constructor(
|
class ArtistImageFetcher private constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val artist: Artist
|
private val size: Size,
|
||||||
|
private val artist: Artist,
|
||||||
) : AuxioFetcher() {
|
) : AuxioFetcher() {
|
||||||
override suspend fun fetch(): FetchResult? {
|
override suspend fun fetch(): FetchResult? {
|
||||||
val results = artist.albums.mapAtMost(4) { album ->
|
val results = artist.albums.mapAtMost(4) { album ->
|
||||||
fetchArt(context, album)
|
fetchArt(context, album)
|
||||||
}
|
}
|
||||||
|
|
||||||
return createMosaic(context, results)
|
return createMosaic(context, results, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Factory : Fetcher.Factory<Artist> {
|
class Factory : Fetcher.Factory<Artist> {
|
||||||
override fun create(data: Artist, options: Options, imageLoader: ImageLoader): Fetcher {
|
override fun create(data: Artist, options: Options, imageLoader: ImageLoader): Fetcher {
|
||||||
return ArtistImageFetcher(options.context, data)
|
return ArtistImageFetcher(options.context, options.size, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,7 +96,8 @@ class ArtistImageFetcher private constructor(
|
||||||
*/
|
*/
|
||||||
class GenreImageFetcher private constructor(
|
class GenreImageFetcher private constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val genre: Genre
|
private val size: Size,
|
||||||
|
private val genre: Genre,
|
||||||
) : AuxioFetcher() {
|
) : AuxioFetcher() {
|
||||||
override suspend fun fetch(): FetchResult? {
|
override suspend fun fetch(): FetchResult? {
|
||||||
val albums = genre.songs.groupBy { it.album }.keys
|
val albums = genre.songs.groupBy { it.album }.keys
|
||||||
|
@ -102,12 +105,12 @@ class GenreImageFetcher private constructor(
|
||||||
fetchArt(context, album)
|
fetchArt(context, album)
|
||||||
}
|
}
|
||||||
|
|
||||||
return createMosaic(context, results)
|
return createMosaic(context, results, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Factory : Fetcher.Factory<Genre> {
|
class Factory : Fetcher.Factory<Genre> {
|
||||||
override fun create(data: Genre, options: Options, imageLoader: ImageLoader): Fetcher {
|
override fun create(data: Genre, options: Options, imageLoader: ImageLoader): Fetcher {
|
||||||
return GenreImageFetcher(options.context, data)
|
return GenreImageFetcher(options.context, options.size, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue