image: add option to restore 1:1 crop behavior

Add an option to restore the old 1:1 crop behavior to the app.

Some people think this looks better, some people like to have youtube
thumbnails in their APICs. Can't really be opinionated here.
This commit is contained in:
Alexander Capehart 2023-06-20 17:25:37 -06:00
parent f1470af586
commit 903a3e561a
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
10 changed files with 110 additions and 34 deletions

View file

@ -48,6 +48,8 @@ import com.google.android.material.shape.MaterialShapeDrawable
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import org.oxycblt.auxio.R
import org.oxycblt.auxio.image.extractor.RoundedRectTransformation
import org.oxycblt.auxio.image.extractor.SquareCropTransformation
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
@ -77,6 +79,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
FrameLayout(context, attrs, defStyleAttr) {
@Inject lateinit var imageLoader: ImageLoader
@Inject lateinit var uiSettings: UISettings
@Inject lateinit var imageSettings: ImageSettings
private val image: ImageView
@ -384,13 +387,19 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
ImageRequest.Builder(context)
.data(songs)
.error(StyledDrawable(context, context.getDrawableCompat(errorRes), iconSizeRes))
.transformations(
RoundedCornersTransformation(cornerRadiusRes?.let(context::getDimen) ?: 0f))
.target(image)
.build()
val cornersTransformation =
RoundedRectTransformation(cornerRadiusRes?.let(context::getDimen) ?: 0f)
if (imageSettings.forceSquareCovers) {
request.transformations(SquareCropTransformation.INSTANCE, cornersTransformation)
} else {
request.transformations(cornersTransformation)
}
// Dispose of any previous image request and load a new image.
CoilUtils.dispose(image)
imageLoader.enqueue(request)
imageLoader.enqueue(request.build())
contentDescription = desc
}

View file

@ -34,6 +34,8 @@ import org.oxycblt.auxio.util.logD
interface ImageSettings : Settings<ImageSettings.Listener> {
/** The strategy to use when loading album covers. */
val coverMode: CoverMode
/** Whether to force all album covers to have a 1:1 aspect ratio. */
val forceSquareCovers: Boolean
interface Listener {
/** Called when [coverMode] changes. */
@ -49,6 +51,9 @@ class ImageSettingsImpl @Inject constructor(@ApplicationContext context: Context
sharedPreferences.getInt(getString(R.string.set_key_cover_mode), Int.MIN_VALUE))
?: CoverMode.MEDIA_STORE
override val forceSquareCovers: Boolean
get() = sharedPreferences.getBoolean(getString(R.string.set_key_square_covers), false)
override fun migrate() {
// Show album covers and Ignore MediaStore covers were unified in 3.0.0
if (sharedPreferences.contains(OLD_KEY_SHOW_COVERS) ||

View file

@ -43,7 +43,6 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import java.io.ByteArrayInputStream
import java.io.InputStream
import javax.inject.Inject
import kotlin.math.min
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.guava.asDeferred
import kotlinx.coroutines.withContext
@ -155,7 +154,7 @@ constructor(
// Get the embedded picture from MediaMetadataRetriever, which will return a full
// ByteArray of the cover without any compression artifacts.
// If its null [i.e there is no embedded cover], than just ignore it and move on
return embeddedPicture?.let { ByteArrayInputStream(it) }.also { release() }
embeddedPicture?.let { ByteArrayInputStream(it) }.also { release() }
}
private suspend fun extractExoplayerCover(album: Album): InputStream? {
@ -212,7 +211,7 @@ constructor(
}
/** Derived from phonograph: https://github.com/kabouzeid/Phonograph */
private fun createMosaic(streams: List<InputStream>, size: Size): FetchResult {
private suspend fun createMosaic(streams: List<InputStream>, size: Size): FetchResult {
// Use whatever size coil gives us to create the mosaic.
val mosaicSize = AndroidSize(size.width.mosaicSize(), size.height.mosaicSize())
val mosaicFrameSize =
@ -234,7 +233,9 @@ constructor(
// Crop the bitmap down to a square so it leaves no empty space
// TODO: Work around this
val bitmap = cropBitmap(BitmapFactory.decodeStream(stream), mosaicFrameSize)
val bitmap =
SquareCropTransformation.INSTANCE.transform(
BitmapFactory.decodeStream(stream), mosaicFrameSize)
canvas.drawBitmap(bitmap, x.toFloat(), y.toFloat(), null)
x += bitmap.width
@ -259,21 +260,4 @@ constructor(
val size = pxOrElse { 512 }
return if (size.mod(2) > 0) size + 1 else size
}
private fun cropBitmap(input: Bitmap, size: Size): Bitmap {
// Find the smaller dimension and then take a center portion of the image that
// has that size.
val dstSize = min(input.width, input.height)
val x = (input.width - dstSize) / 2
val y = (input.height - dstSize) / 2
val dst = Bitmap.createBitmap(input, x, y, dstSize, dstSize)
val desiredWidth = size.width.pxOrElse { dstSize }
val desiredHeight = size.height.pxOrElse { dstSize }
if (dstSize != desiredWidth || dstSize != desiredHeight) {
// Image is not the desired size, upscale it.
return Bitmap.createScaledBitmap(dst, desiredWidth, desiredHeight, true)
}
return dst
}
}

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2023 Auxio Project
* RoundedCornersTransformation.kt is part of Auxio.
* RoundedRectTransformation.kt is part of Auxio.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.image
package org.oxycblt.auxio.image.extractor
import android.graphics.Bitmap
import android.graphics.Bitmap.createBitmap
@ -43,7 +43,7 @@ import kotlin.math.roundToInt
*
* @author Coil Team, Alexander Capehart (OxygenCobalt)
*/
class RoundedCornersTransformation(
class RoundedRectTransformation(
@Px private val topLeft: Float = 0f,
@Px private val topRight: Float = 0f,
@Px private val bottomLeft: Float = 0f,
@ -122,7 +122,7 @@ class RoundedCornersTransformation(
override fun equals(other: Any?): Boolean {
if (this === other) return true
return other is RoundedCornersTransformation &&
return other is RoundedRectTransformation &&
topLeft == other.topLeft &&
topRight == other.topRight &&
bottomLeft == other.bottomLeft &&

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 2023 Auxio Project
* SquareCropTransformation.kt is part of Auxio.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.image.extractor
import android.graphics.Bitmap
import coil.size.Size
import coil.size.pxOrElse
import coil.transform.Transformation
import kotlin.math.min
/**
* A [Transformation] that performs a center crop-style transformation on an image. Allowing this
* behavior to be intrinsic without any view configuration.
*
* @author Alexander Capehart (OxygenCobalt)
*/
class SquareCropTransformation : Transformation {
override val cacheKey: String
get() = "SquareCropTransformation"
override suspend fun transform(input: Bitmap, size: Size): Bitmap {
// Find the smaller dimension and then take a center portion of the image that
// has that size.
val dstSize = min(input.width, input.height)
val x = (input.width - dstSize) / 2
val y = (input.height - dstSize) / 2
val dst = Bitmap.createBitmap(input, x, y, dstSize, dstSize)
val desiredWidth = size.width.pxOrElse { dstSize }
val desiredHeight = size.height.pxOrElse { dstSize }
if (dstSize != desiredWidth || dstSize != desiredHeight) {
// Image is not the desired size, upscale it.
return Bitmap.createScaledBitmap(dst, desiredWidth, desiredHeight, true)
}
return dst
}
companion object {
/** A re-usable instance. */
val INSTANCE = SquareCropTransformation()
}
}

View file

@ -47,7 +47,8 @@ class MusicPreferenceFragment : BasePreferenceFragment(R.xml.preferences_music)
}
override fun onSetupPreference(preference: Preference) {
if (preference.key == getString(R.string.set_key_cover_mode)) {
if (preference.key == getString(R.string.set_key_cover_mode) ||
preference.key == getString(R.string.set_key_square_covers)) {
logD("Configuring cover mode setting")
preference.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _, _ ->

View file

@ -27,7 +27,8 @@ import javax.inject.Inject
import org.oxycblt.auxio.R
import org.oxycblt.auxio.image.BitmapProvider
import org.oxycblt.auxio.image.ImageSettings
import org.oxycblt.auxio.image.RoundedCornersTransformation
import org.oxycblt.auxio.image.extractor.RoundedRectTransformation
import org.oxycblt.auxio.image.extractor.SquareCropTransformation
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.queue.Queue
@ -98,10 +99,19 @@ constructor(
return if (cornerRadius > 0) {
// If rounded, reduce the bitmap size further to obtain more pronounced
// rounded corners.
builder
.size(getSafeRemoteViewsImageSize(context, 10f))
.transformations(RoundedCornersTransformation(cornerRadius.toFloat()))
builder.size(getSafeRemoteViewsImageSize(context, 10f))
val cornersTransformation =
RoundedRectTransformation(cornerRadius.toFloat())
if (imageSettings.forceSquareCovers) {
builder.transformations(
SquareCropTransformation.INSTANCE, cornersTransformation)
} else {
builder.transformations(cornersTransformation)
}
} else {
if (imageSettings.forceSquareCovers) {
builder.transformations(SquareCropTransformation.INSTANCE)
}
builder.size(getSafeRemoteViewsImageSize(context))
}
}

View file

@ -15,6 +15,7 @@
<string name="set_key_observing" translatable="false">auxio_observing</string>
<string name="set_key_music_dirs" translatable="false">auxio_music_dirs</string>
<string name="set_key_cover_mode" translatable="false">auxio_cover_mode</string>
<string name="set_key_square_covers" translatable="false">auxio_square_covers</string>
<string name="set_key_music_dirs_include" translatable="false">auxio_include_dirs</string>
<string name="set_key_exclude_non_music" translatable="false">auxio_exclude_non_music</string>
<string name="set_key_separators" translatable="false">auxio_separators</string>

View file

@ -239,6 +239,8 @@
<string name="set_cover_mode_off">Off</string>
<string name="set_cover_mode_media_store">Fast</string>
<string name="set_cover_mode_quality">High quality</string>
<string name="set_square_covers">Force square album covers</string>
<string name="set_square_covers_desc">Crop all album covers to a 1:1 aspect ratio</string>
<string name="set_audio">Audio</string>
<string name="set_audio_desc">Configure sound and playback behavior</string>

View file

@ -43,5 +43,11 @@
app:key="@string/set_key_cover_mode"
app:title="@string/set_cover_mode" />
<SwitchPreferenceCompat
app:defaultValue="false"
app:key="@string/set_key_square_covers"
app:summary="@string/set_square_covers_desc"
app:title="@string/set_square_covers" />
</PreferenceCategory>
</PreferenceScreen>