From 903a3e561aa69174b935256f3cfdefe162912286 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Tue, 20 Jun 2023 17:25:37 -0600 Subject: [PATCH] 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. --- .../java/org/oxycblt/auxio/image/CoverView.kt | 17 ++++-- .../org/oxycblt/auxio/image/ImageSettings.kt | 5 ++ .../auxio/image/extractor/CoverExtractor.kt | 26 ++------- .../RoundedRectTransformation.kt} | 8 +-- .../extractor/SquareCropTransformation.kt | 58 +++++++++++++++++++ .../categories/MusicPreferenceFragment.kt | 3 +- .../oxycblt/auxio/widgets/WidgetComponent.kt | 18 ++++-- app/src/main/res/values/settings.xml | 1 + app/src/main/res/values/strings.xml | 2 + app/src/main/res/xml/preferences_music.xml | 6 ++ 10 files changed, 110 insertions(+), 34 deletions(-) rename app/src/main/java/org/oxycblt/auxio/image/{RoundedCornersTransformation.kt => extractor/RoundedRectTransformation.kt} (96%) create mode 100644 app/src/main/java/org/oxycblt/auxio/image/extractor/SquareCropTransformation.kt diff --git a/app/src/main/java/org/oxycblt/auxio/image/CoverView.kt b/app/src/main/java/org/oxycblt/auxio/image/CoverView.kt index b2912595c..1ec38068e 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/CoverView.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/CoverView.kt @@ -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 } diff --git a/app/src/main/java/org/oxycblt/auxio/image/ImageSettings.kt b/app/src/main/java/org/oxycblt/auxio/image/ImageSettings.kt index a4c13c4c9..1a9a01b24 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/ImageSettings.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/ImageSettings.kt @@ -34,6 +34,8 @@ import org.oxycblt.auxio.util.logD interface ImageSettings : Settings { /** 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) || diff --git a/app/src/main/java/org/oxycblt/auxio/image/extractor/CoverExtractor.kt b/app/src/main/java/org/oxycblt/auxio/image/extractor/CoverExtractor.kt index 6ca126256..537d8e874 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/extractor/CoverExtractor.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/extractor/CoverExtractor.kt @@ -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, size: Size): FetchResult { + private suspend fun createMosaic(streams: List, 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 - } } diff --git a/app/src/main/java/org/oxycblt/auxio/image/RoundedCornersTransformation.kt b/app/src/main/java/org/oxycblt/auxio/image/extractor/RoundedRectTransformation.kt similarity index 96% rename from app/src/main/java/org/oxycblt/auxio/image/RoundedCornersTransformation.kt rename to app/src/main/java/org/oxycblt/auxio/image/extractor/RoundedRectTransformation.kt index c25770ec4..c8d3ee145 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/RoundedCornersTransformation.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/extractor/RoundedRectTransformation.kt @@ -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 . */ -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 && diff --git a/app/src/main/java/org/oxycblt/auxio/image/extractor/SquareCropTransformation.kt b/app/src/main/java/org/oxycblt/auxio/image/extractor/SquareCropTransformation.kt new file mode 100644 index 000000000..57f03dbef --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/image/extractor/SquareCropTransformation.kt @@ -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 . + */ + +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() + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/settings/categories/MusicPreferenceFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/categories/MusicPreferenceFragment.kt index b0b59d02b..9e1e83af5 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/categories/MusicPreferenceFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/categories/MusicPreferenceFragment.kt @@ -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 { _, _ -> diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt index 1cb3f599c..451dcabd7 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt @@ -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)) } } diff --git a/app/src/main/res/values/settings.xml b/app/src/main/res/values/settings.xml index faab5d5cd..f3c956766 100644 --- a/app/src/main/res/values/settings.xml +++ b/app/src/main/res/values/settings.xml @@ -15,6 +15,7 @@ auxio_observing auxio_music_dirs auxio_cover_mode + auxio_square_covers auxio_include_dirs auxio_exclude_non_music auxio_separators diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 676133e13..95e6c348e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -239,6 +239,8 @@ Off Fast High quality + Force square album covers + Crop all album covers to a 1:1 aspect ratio Audio Configure sound and playback behavior diff --git a/app/src/main/res/xml/preferences_music.xml b/app/src/main/res/xml/preferences_music.xml index a46bb1025..86a3a6b03 100644 --- a/app/src/main/res/xml/preferences_music.xml +++ b/app/src/main/res/xml/preferences_music.xml @@ -43,5 +43,11 @@ app:key="@string/set_key_cover_mode" app:title="@string/set_cover_mode" /> + + \ No newline at end of file