coil: make mosaics use squareframetransform

Change createMosaic to use SquareFrameTransform.

This should help fix a theoretical bug where non-1:1 album covers will
be oddly stretched in a generated mosaic, as all album covers will be
forcefully cropped to a 1:1 ratio.
This commit is contained in:
OxygenCobalt 2022-02-24 14:55:35 -07:00
parent 61dbfe3185
commit 9171f9a3b4
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
10 changed files with 36 additions and 18 deletions

View file

@ -40,6 +40,8 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
/**
* The single [AppCompatActivity] for Auxio.
* TODO: Add a new view for crashes with a stack trace
* TODO: Custom language support
*/
class MainActivity : AppCompatActivity() {
private val playbackModel: PlaybackViewModel by viewModels()
@ -104,10 +106,10 @@ class MainActivity : AppCompatActivity() {
// The black theme has a completely separate set of styles since style attributes cannot
// be modified at runtime.
if (isNight && settingsManager.useBlackTheme) {
logD("Applying black theme [with accent $accent]")
logD("Applying black theme [accent $accent]")
setTheme(accent.blackTheme)
} else {
logD("Applying normal theme [with accent $accent]")
logD("Applying normal theme [accent $accent]")
setTheme(accent.theme)
}
}

View file

@ -42,6 +42,7 @@ import org.oxycblt.auxio.util.logW
* A wrapper around the home fragment that shows the playback fragment and controls
* the more high-level navigation features.
* @author OxygenCobalt
* TODO: Add a new view with a stack trace whenever the music loading process fails.
*/
class MainFragment : Fragment() {
private val playbackModel: PlaybackViewModel by activityViewModels()

View file

@ -35,6 +35,7 @@ import android.util.Size as AndroidSize
/**
* The base implementation for all image fetchers in Auxio.
* @author OxygenCobalt
* TODO: Artist images
*/
abstract class AuxioFetcher : Fetcher {
private val settingsManager = SettingsManager.getInstance()
@ -204,7 +205,7 @@ abstract class AuxioFetcher : Fetcher {
* Create a mosaic image from multiple streams of image data, Code adapted from Phonograph
* https://github.com/kabouzeid/Phonograph
*/
protected fun createMosaic(context: Context, streams: List<InputStream>, size: Size): FetchResult? {
protected suspend fun createMosaic(context: Context, streams: List<InputStream>, size: Size): FetchResult? {
if (streams.size < 4) {
return streams.firstOrNull()?.let { stream ->
return SourceResult(
@ -219,7 +220,9 @@ abstract class AuxioFetcher : Fetcher {
// get a symmetrical mosaic [and to prevent bugs]. If there is no size, default to a
// 512x512 mosaic.
val mosaicSize = AndroidSize(size.width.mosaicSize(), size.height.mosaicSize())
val increment = AndroidSize(mosaicSize.width / 2, mosaicSize.height / 2)
val mosaicFrameSize = Size(
Dimension(mosaicSize.width / 2), Dimension(mosaicSize.height / 2)
)
val mosaicBitmap = Bitmap.createBitmap(
mosaicSize.width,
@ -238,20 +241,20 @@ abstract class AuxioFetcher : Fetcher {
break
}
val bitmap = Bitmap.createScaledBitmap(
BitmapFactory.decodeStream(stream),
increment.width,
increment.height,
true
)
// Run the bitmap through a transform to make sure it's square
val bitmap = SquareFrameTransform.INSTANCE
.transform(
BitmapFactory.decodeStream(stream),
mosaicFrameSize
)
canvas.drawBitmap(bitmap, x.toFloat(), y.toFloat(), null)
x += increment.width
x += bitmap.width
if (x == mosaicSize.width) {
x = 0
y += increment.height
y += bitmap.height
}
}

View file

@ -64,10 +64,9 @@ fun ImageView.bindGenreImage(genre: Genre?) = load(genre, R.drawable.ic_genre)
fun <T : Music> ImageView.load(music: T?, @DrawableRes error: Int) {
dispose()
load(music) {
error(error)
transformations(SquareFrameTransform())
transformations(SquareFrameTransform.INSTANCE)
}
}

View file

@ -10,6 +10,11 @@ import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.util.getColorSafe
import org.oxycblt.auxio.util.stateList
/**
* An [AppCompatImageView] that applies the specified cornerRadius attribute if the user
* has enabled the "Round album covers" option. We don't round album covers by default as
* it desecrates album artwork, but if the user desires it we do have an option to enable it.
*/
class RoundableImageView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
@ -17,7 +22,7 @@ class RoundableImageView @JvmOverloads constructor(
) : AppCompatImageView(context, attrs, defStyleAttr) {
init {
val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.RoundableImageView)
val cornerRadius = styledAttrs.getDimension(R.styleable.RoundableImageView_cornerRadius, 0.0f)
val cornerRadius = styledAttrs.getDimension(R.styleable.RoundableImageView_cornerRadius, 0f)
styledAttrs.recycle()
background = MaterialShapeDrawable().apply {
@ -29,8 +34,6 @@ class RoundableImageView @JvmOverloads constructor(
override fun onAttachedToWindow() {
super.onAttachedToWindow()
// We don't round album covers by default as it desecrates album artwork, but we do
// provide an option if one wants it.
// As for why we use clipToOutline instead of coils RoundedCornersTransformation, the radii
// of an image's corners is dependent on the actual dimensions of the image, which would
// force us to resize all images to a fixed size. clipToOutline is pretty much always

View file

@ -37,4 +37,8 @@ class SquareFrameTransform : Transformation {
return dst
}
companion object {
val INSTANCE = SquareFrameTransform()
}
}

View file

@ -55,6 +55,7 @@ import org.oxycblt.auxio.util.logTraceOrThrow
* The main "Launching Point" fragment of Auxio, allowing navigation to the detail
* views for each respective item.
* @author OxygenCobalt
* TODO: Make tabs invisible when there is only one
*/
class HomeFragment : Fragment() {
private val playbackModel: PlaybackViewModel by activityViewModels()
@ -163,7 +164,7 @@ class HomeFragment : Fragment() {
playbackModel.shuffleAll()
}
// --- VIEWMODEL SETUP ---
// --- VIEWMODEL SETUP ---
// There is no way a fast scrolling event can continue across a re-create. Reset it.
homeModel.updateFastScrolling(false)

View file

@ -36,6 +36,7 @@ import java.lang.Exception
* The main storage for music items.
* Getting an instance of this object is more complicated as it loads asynchronously.
* See the companion object for more.
* TODO: Add automatic rescanning [major change]
* @author OxygenCobalt
*/
class MusicStore private constructor() {

View file

@ -36,6 +36,7 @@ import org.oxycblt.auxio.util.stateList
* A custom view that bundles together a seekbar with a current duration and a total duration.
* The sub-views are specifically laid out so that the seekbar has an adequate touch height while
* still not having gobs of whitespace everywhere.
* TODO: Add smooth seeking [i.e seeking in sub-second values]
* @author OxygenCobalt
*/
@SuppressLint("RestrictedApi")

View file

@ -55,6 +55,9 @@ fun Fragment.newMenu(anchor: View, data: Item, flag: Int = ActionMenu.FLAG_NONE)
* @param flag Any extra flags to accompany the data. See [FLAG_NONE], [FLAG_IN_ALBUM], [FLAG_IN_ARTIST], [FLAG_IN_GENRE] for more details.
* @throws IllegalStateException When there is no menu for this specific datatype/flag
* @author OxygenCobalt
* TODO: Stop scrolling when a menu is open
* TODO: Prevent duplicate menus from showing up
* TODO: Maybe replace this with a bottom sheet?
*/
class ActionMenu(
activity: AppCompatActivity,