diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt
index cfa4bd7b8..9dee9473b 100644
--- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt
@@ -514,7 +514,7 @@ class MainFragment :
val exploreNavController = binding.exploreNavHost.findNavController()
// TODO: Debug why this fails sometimes on the playback sheet
- // TODO: Add playlist editing
+ // TODO: Add playlist editing to enabled check
// TODO: Can this be split up?
isEnabled =
diff --git a/app/src/main/java/org/oxycblt/auxio/image/BitmapProvider.kt b/app/src/main/java/org/oxycblt/auxio/image/BitmapProvider.kt
index 76a55dea1..ad81c25a9 100644
--- a/app/src/main/java/org/oxycblt/auxio/image/BitmapProvider.kt
+++ b/app/src/main/java/org/oxycblt/auxio/image/BitmapProvider.kt
@@ -27,7 +27,6 @@ import coil.request.ImageRequest
import coil.size.Size
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
-import org.oxycblt.auxio.image.extractor.SquareFrameTransform
import org.oxycblt.auxio.music.Song
/**
@@ -98,9 +97,6 @@ constructor(
.data(listOf(song))
// Use ORIGINAL sizing, as we are not loading into any View-like component.
.size(Size.ORIGINAL))
- // Override the target in order to deliver the bitmap to the given
- // listener.
- .transformations(SquareFrameTransform.INSTANCE)
.target(
onSuccess = {
synchronized(this) {
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 b783f849c..86707f22e 100644
--- a/app/src/main/java/org/oxycblt/auxio/image/CoverView.kt
+++ b/app/src/main/java/org/oxycblt/auxio/image/CoverView.kt
@@ -42,7 +42,6 @@ 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.SquareFrameTransform
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
@@ -63,8 +62,6 @@ import org.oxycblt.auxio.util.getInteger
* itself can be overridden if populated like a normal [FrameLayout].
*
* @author Alexander Capehart (OxygenCobalt)
- *
- * TODO: Enable non-square covers as soon as I can confirm that my workaround is okay
*/
@AndroidEntryPoint
class CoverView
@@ -142,7 +139,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
child.apply {
// If there are rounded corners, we want to make sure view content will be cropped
// with it.
- clipToOutline = true
+ clipToOutline = this != image
background =
MaterialShapeDrawable().apply {
fillColor = context.getColorCompat(R.color.sel_cover_bg)
@@ -316,7 +313,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
ImageRequest.Builder(context)
.data(songs)
.error(StyledDrawable(context, context.getDrawableCompat(errorRes)))
- .transformations(SquareFrameTransform.INSTANCE)
+ .transformations(RoundedCornersTransformation(cornerRadius))
.target(image)
.build()
// Dispose of any previous image request and load a new image.
@@ -335,8 +332,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
override fun draw(canvas: Canvas) {
// Resize the drawable such that it's always 1/4 the size of the image and
// centered in the middle of the canvas.
- val adjustWidth = bounds.width() / 4
- val adjustHeight = bounds.height() / 4
+ val adjustWidth = inner.bounds.width() / 4
+ val adjustHeight = inner.bounds.height() / 4
inner.bounds.set(
adjustWidth,
adjustHeight,
diff --git a/app/src/main/java/org/oxycblt/auxio/image/RoundedCornersTransformation.kt b/app/src/main/java/org/oxycblt/auxio/image/RoundedCornersTransformation.kt
new file mode 100644
index 000000000..c25770ec4
--- /dev/null
+++ b/app/src/main/java/org/oxycblt/auxio/image/RoundedCornersTransformation.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2023 Auxio Project
+ * RoundedCornersTransformation.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
+
+import android.graphics.Bitmap
+import android.graphics.Bitmap.createBitmap
+import android.graphics.BitmapShader
+import android.graphics.Color
+import android.graphics.Matrix
+import android.graphics.Paint
+import android.graphics.Path
+import android.graphics.PorterDuff
+import android.graphics.RectF
+import android.graphics.Shader
+import androidx.annotation.Px
+import androidx.core.graphics.applyCanvas
+import coil.decode.DecodeUtils
+import coil.size.Scale
+import coil.size.Size
+import coil.size.pxOrElse
+import coil.transform.Transformation
+import kotlin.math.roundToInt
+
+/**
+ * A vendoring of [coil.transform.RoundedCornersTransformation] that can handle non-1:1 aspect ratio
+ * images without cropping them.
+ *
+ * @author Coil Team, Alexander Capehart (OxygenCobalt)
+ */
+class RoundedCornersTransformation(
+ @Px private val topLeft: Float = 0f,
+ @Px private val topRight: Float = 0f,
+ @Px private val bottomLeft: Float = 0f,
+ @Px private val bottomRight: Float = 0f
+) : Transformation {
+
+ constructor(@Px radius: Float) : this(radius, radius, radius, radius)
+
+ init {
+ require(topLeft >= 0 && topRight >= 0 && bottomLeft >= 0 && bottomRight >= 0) {
+ "All radii must be >= 0."
+ }
+ }
+
+ override val cacheKey = "${javaClass.name}-$topLeft,$topRight,$bottomLeft,$bottomRight"
+
+ override suspend fun transform(input: Bitmap, size: Size): Bitmap {
+ val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG)
+
+ val (outputWidth, outputHeight) = calculateOutputSize(input, size)
+
+ val output = createBitmap(outputWidth, outputHeight, input.config)
+ output.applyCanvas {
+ drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
+
+ val matrix = Matrix()
+ val multiplier =
+ DecodeUtils.computeSizeMultiplier(
+ srcWidth = input.width,
+ srcHeight = input.height,
+ dstWidth = outputWidth,
+ dstHeight = outputHeight,
+ scale = Scale.FILL)
+ .toFloat()
+ val dx = (outputWidth - multiplier * input.width) / 2
+ val dy = (outputHeight - multiplier * input.height) / 2
+ matrix.setTranslate(dx, dy)
+ matrix.preScale(multiplier, multiplier)
+
+ val shader = BitmapShader(input, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
+ shader.setLocalMatrix(matrix)
+ paint.shader = shader
+
+ val radii =
+ floatArrayOf(
+ topLeft,
+ topLeft,
+ topRight,
+ topRight,
+ bottomRight,
+ bottomRight,
+ bottomLeft,
+ bottomLeft,
+ )
+ val rect = RectF(0f, 0f, width.toFloat(), height.toFloat())
+ val path = Path().apply { addRoundRect(rect, radii, Path.Direction.CW) }
+ drawPath(path, paint)
+ }
+
+ return output
+ }
+
+ private fun calculateOutputSize(input: Bitmap, size: Size): Pair {
+ // MODIFICATION: Remove short-circuiting for original size and input size
+ val multiplier =
+ DecodeUtils.computeSizeMultiplier(
+ srcWidth = input.width,
+ srcHeight = input.height,
+ dstWidth = size.width.pxOrElse { Int.MIN_VALUE },
+ dstHeight = size.height.pxOrElse { Int.MIN_VALUE },
+ scale = Scale.FIT)
+ val outputWidth = (multiplier * input.width).roundToInt()
+ val outputHeight = (multiplier * input.height).roundToInt()
+ return outputWidth to outputHeight
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ return other is RoundedCornersTransformation &&
+ topLeft == other.topLeft &&
+ topRight == other.topRight &&
+ bottomLeft == other.bottomLeft &&
+ bottomRight == other.bottomRight
+ }
+
+ override fun hashCode(): Int {
+ var result = topLeft.hashCode()
+ result = 31 * result + topRight.hashCode()
+ result = 31 * result + bottomLeft.hashCode()
+ result = 31 * result + bottomRight.hashCode()
+ return result
+ }
+}
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 c835f5c63..ef9af4c3f 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,6 +43,7 @@ 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
@@ -224,10 +225,9 @@ constructor(
break
}
- // Run the bitmap through a transform to reflect the configuration of other images.
- val bitmap =
- SquareFrameTransform.INSTANCE.transform(
- BitmapFactory.decodeStream(stream), mosaicFrameSize)
+ // Crop the bitmap down to a square so it leaves no empty space
+ // TODO: Work around this
+ val bitmap = cropBitmap(BitmapFactory.decodeStream(stream), mosaicFrameSize)
canvas.drawBitmap(bitmap, x.toFloat(), y.toFloat(), null)
x += bitmap.width
@@ -252,4 +252,21 @@ 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/extractor/SquareFrameTransform.kt b/app/src/main/java/org/oxycblt/auxio/image/extractor/SquareFrameTransform.kt
deleted file mode 100644
index b8d9de4e8..000000000
--- a/app/src/main/java/org/oxycblt/auxio/image/extractor/SquareFrameTransform.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (c) 2022 Auxio Project
- * SquareFrameTransform.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 SquareFrameTransform : Transformation {
- override val cacheKey = "SquareFrameTransform"
-
- 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 = SquareFrameTransform()
- }
-}
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/persist/PersistenceDatabase.kt b/app/src/main/java/org/oxycblt/auxio/playback/persist/PersistenceDatabase.kt
index 545038207..93e387068 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/persist/PersistenceDatabase.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/persist/PersistenceDatabase.kt
@@ -127,6 +127,7 @@ interface QueueDao {
}
// TODO: Figure out how to get RepeatMode to map to an int instead of a string
+// TODO: Use intrinsic table names rather than custom names
@Entity(tableName = PlaybackState.TABLE_NAME)
data class PlaybackState(
@PrimaryKey val id: Int,
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 8aba7f42c..1cb3f599c 100644
--- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt
+++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt
@@ -22,13 +22,12 @@ import android.content.Context
import android.graphics.Bitmap
import android.os.Build
import coil.request.ImageRequest
-import coil.transform.RoundedCornersTransformation
import dagger.hilt.android.qualifiers.ApplicationContext
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.extractor.SquareFrameTransform
+import org.oxycblt.auxio.image.RoundedCornersTransformation
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.queue.Queue
@@ -101,9 +100,7 @@ constructor(
// rounded corners.
builder
.size(getSafeRemoteViewsImageSize(context, 10f))
- .transformations(
- SquareFrameTransform.INSTANCE,
- RoundedCornersTransformation(cornerRadius.toFloat()))
+ .transformations(RoundedCornersTransformation(cornerRadius.toFloat()))
} else {
builder.size(getSafeRemoteViewsImageSize(context))
}
diff --git a/app/src/main/res/layout/item_disc_header.xml b/app/src/main/res/layout/item_disc_header.xml
index 96f7ff7cd..6ddc08409 100644
--- a/app/src/main/res/layout/item_disc_header.xml
+++ b/app/src/main/res/layout/item_disc_header.xml
@@ -25,7 +25,7 @@
android:id="@+id/disc_icon"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:src="@drawable/ic_add_24"
+ android:src="@drawable/ic_album_24"
android:scaleType="center"
app:tint="@color/sel_on_cover_bg"
tools:ignore="ContentDescription" />