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:
parent
61dbfe3185
commit
9171f9a3b4
10 changed files with 36 additions and 18 deletions
|
@ -40,6 +40,8 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The single [AppCompatActivity] for Auxio.
|
* The single [AppCompatActivity] for Auxio.
|
||||||
|
* TODO: Add a new view for crashes with a stack trace
|
||||||
|
* TODO: Custom language support
|
||||||
*/
|
*/
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
private val playbackModel: PlaybackViewModel by viewModels()
|
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
|
// The black theme has a completely separate set of styles since style attributes cannot
|
||||||
// be modified at runtime.
|
// be modified at runtime.
|
||||||
if (isNight && settingsManager.useBlackTheme) {
|
if (isNight && settingsManager.useBlackTheme) {
|
||||||
logD("Applying black theme [with accent $accent]")
|
logD("Applying black theme [accent $accent]")
|
||||||
setTheme(accent.blackTheme)
|
setTheme(accent.blackTheme)
|
||||||
} else {
|
} else {
|
||||||
logD("Applying normal theme [with accent $accent]")
|
logD("Applying normal theme [accent $accent]")
|
||||||
setTheme(accent.theme)
|
setTheme(accent.theme)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@ import org.oxycblt.auxio.util.logW
|
||||||
* A wrapper around the home fragment that shows the playback fragment and controls
|
* A wrapper around the home fragment that shows the playback fragment and controls
|
||||||
* the more high-level navigation features.
|
* the more high-level navigation features.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
|
* TODO: Add a new view with a stack trace whenever the music loading process fails.
|
||||||
*/
|
*/
|
||||||
class MainFragment : Fragment() {
|
class MainFragment : Fragment() {
|
||||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||||
|
|
|
@ -35,6 +35,7 @@ import android.util.Size as AndroidSize
|
||||||
/**
|
/**
|
||||||
* The base implementation for all image fetchers in Auxio.
|
* The base implementation for all image fetchers in Auxio.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
|
* TODO: Artist images
|
||||||
*/
|
*/
|
||||||
abstract class AuxioFetcher : Fetcher {
|
abstract class AuxioFetcher : Fetcher {
|
||||||
private val settingsManager = SettingsManager.getInstance()
|
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
|
* 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>, size: Size): FetchResult? {
|
protected suspend fun createMosaic(context: Context, streams: List<InputStream>, size: Size): FetchResult? {
|
||||||
if (streams.size < 4) {
|
if (streams.size < 4) {
|
||||||
return streams.firstOrNull()?.let { stream ->
|
return streams.firstOrNull()?.let { stream ->
|
||||||
return SourceResult(
|
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
|
// get a symmetrical mosaic [and to prevent bugs]. If there is no size, default to a
|
||||||
// 512x512 mosaic.
|
// 512x512 mosaic.
|
||||||
val mosaicSize = AndroidSize(size.width.mosaicSize(), size.height.mosaicSize())
|
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(
|
val mosaicBitmap = Bitmap.createBitmap(
|
||||||
mosaicSize.width,
|
mosaicSize.width,
|
||||||
|
@ -238,20 +241,20 @@ abstract class AuxioFetcher : Fetcher {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
val bitmap = Bitmap.createScaledBitmap(
|
// Run the bitmap through a transform to make sure it's square
|
||||||
|
val bitmap = SquareFrameTransform.INSTANCE
|
||||||
|
.transform(
|
||||||
BitmapFactory.decodeStream(stream),
|
BitmapFactory.decodeStream(stream),
|
||||||
increment.width,
|
mosaicFrameSize
|
||||||
increment.height,
|
|
||||||
true
|
|
||||||
)
|
)
|
||||||
|
|
||||||
canvas.drawBitmap(bitmap, x.toFloat(), y.toFloat(), null)
|
canvas.drawBitmap(bitmap, x.toFloat(), y.toFloat(), null)
|
||||||
|
|
||||||
x += increment.width
|
x += bitmap.width
|
||||||
|
|
||||||
if (x == mosaicSize.width) {
|
if (x == mosaicSize.width) {
|
||||||
x = 0
|
x = 0
|
||||||
y += increment.height
|
y += bitmap.height
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
fun <T : Music> ImageView.load(music: T?, @DrawableRes error: Int) {
|
||||||
dispose()
|
dispose()
|
||||||
|
|
||||||
load(music) {
|
load(music) {
|
||||||
error(error)
|
error(error)
|
||||||
transformations(SquareFrameTransform())
|
transformations(SquareFrameTransform.INSTANCE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,11 @@ import org.oxycblt.auxio.settings.SettingsManager
|
||||||
import org.oxycblt.auxio.util.getColorSafe
|
import org.oxycblt.auxio.util.getColorSafe
|
||||||
import org.oxycblt.auxio.util.stateList
|
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(
|
class RoundableImageView @JvmOverloads constructor(
|
||||||
context: Context,
|
context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
|
@ -17,7 +22,7 @@ class RoundableImageView @JvmOverloads constructor(
|
||||||
) : AppCompatImageView(context, attrs, defStyleAttr) {
|
) : AppCompatImageView(context, attrs, defStyleAttr) {
|
||||||
init {
|
init {
|
||||||
val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.RoundableImageView)
|
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()
|
styledAttrs.recycle()
|
||||||
|
|
||||||
background = MaterialShapeDrawable().apply {
|
background = MaterialShapeDrawable().apply {
|
||||||
|
@ -29,8 +34,6 @@ class RoundableImageView @JvmOverloads constructor(
|
||||||
override fun onAttachedToWindow() {
|
override fun onAttachedToWindow() {
|
||||||
super.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
|
// 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
|
// 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
|
// force us to resize all images to a fixed size. clipToOutline is pretty much always
|
||||||
|
|
|
@ -37,4 +37,8 @@ class SquareFrameTransform : Transformation {
|
||||||
|
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val INSTANCE = SquareFrameTransform()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,7 @@ import org.oxycblt.auxio.util.logTraceOrThrow
|
||||||
* The main "Launching Point" fragment of Auxio, allowing navigation to the detail
|
* The main "Launching Point" fragment of Auxio, allowing navigation to the detail
|
||||||
* views for each respective item.
|
* views for each respective item.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
|
* TODO: Make tabs invisible when there is only one
|
||||||
*/
|
*/
|
||||||
class HomeFragment : Fragment() {
|
class HomeFragment : Fragment() {
|
||||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||||
|
@ -163,7 +164,7 @@ class HomeFragment : Fragment() {
|
||||||
playbackModel.shuffleAll()
|
playbackModel.shuffleAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- VIEWMODEL SETUP ---
|
// --- VIEWMODEL SETUP ---
|
||||||
|
|
||||||
// There is no way a fast scrolling event can continue across a re-create. Reset it.
|
// There is no way a fast scrolling event can continue across a re-create. Reset it.
|
||||||
homeModel.updateFastScrolling(false)
|
homeModel.updateFastScrolling(false)
|
||||||
|
|
|
@ -36,6 +36,7 @@ import java.lang.Exception
|
||||||
* The main storage for music items.
|
* The main storage for music items.
|
||||||
* Getting an instance of this object is more complicated as it loads asynchronously.
|
* Getting an instance of this object is more complicated as it loads asynchronously.
|
||||||
* See the companion object for more.
|
* See the companion object for more.
|
||||||
|
* TODO: Add automatic rescanning [major change]
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class MusicStore private constructor() {
|
class MusicStore private constructor() {
|
||||||
|
|
|
@ -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.
|
* 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
|
* The sub-views are specifically laid out so that the seekbar has an adequate touch height while
|
||||||
* still not having gobs of whitespace everywhere.
|
* still not having gobs of whitespace everywhere.
|
||||||
|
* TODO: Add smooth seeking [i.e seeking in sub-second values]
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
|
|
|
@ -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.
|
* @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
|
* @throws IllegalStateException When there is no menu for this specific datatype/flag
|
||||||
* @author OxygenCobalt
|
* @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(
|
class ActionMenu(
|
||||||
activity: AppCompatActivity,
|
activity: AppCompatActivity,
|
||||||
|
|
Loading…
Reference in a new issue