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:
OxygenCobalt 2021-11-26 11:12:01 -07:00
parent 22c54993ed
commit c0b2dee3ff
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
2 changed files with 32 additions and 23 deletions

View file

@ -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
}
} }

View file

@ -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)
} }
} }
} }