image: fix seam appearing on some images
Fix an issue where a seam might appear on some covers when rounded covers was enabled. This was caused by a poor usage of clipToOutline. Replace with simply stacking existing image instances on top of eachother.
This commit is contained in:
parent
3d19794d63
commit
09442c475f
4 changed files with 78 additions and 193 deletions
|
@ -199,8 +199,6 @@ class DetailViewModel(application: Application) :
|
|||
null
|
||||
}
|
||||
|
||||
// Ensure that we don't include the functionally useless
|
||||
// "audio/raw" mime type
|
||||
MimeType(song.mimeType.fromExtension, formatMimeType)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,133 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Auxio Project
|
||||
*
|
||||
* 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
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.ColorFilter
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.AttributeSet
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import coil.dispose
|
||||
import coil.load
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.music.Album
|
||||
import org.oxycblt.auxio.music.Artist
|
||||
import org.oxycblt.auxio.music.Genre
|
||||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.util.getColorStateListSafe
|
||||
import org.oxycblt.auxio.util.getDrawableSafe
|
||||
|
||||
/**
|
||||
* The base class for Auxio's images. Do not use this class outside of this module.
|
||||
*
|
||||
* Default behavior includes the addition of a tonal background and automatic icon sizing. Other
|
||||
* behavior is implemented by [StyledImageView] and [ImageGroup].
|
||||
*
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
open class BaseStyledImageView
|
||||
@JvmOverloads
|
||||
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
||||
AppCompatImageView(context, attrs, defStyleAttr) {
|
||||
private var staticIcon = 0
|
||||
|
||||
init {
|
||||
val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.StyledImageView)
|
||||
staticIcon = styledAttrs.getResourceId(R.styleable.StyledImageView_staticIcon, -1)
|
||||
styledAttrs.recycle()
|
||||
|
||||
if (staticIcon > -1) {
|
||||
@Suppress("LeakingThis")
|
||||
setImageDrawable(StyledDrawable(context, context.getDrawableSafe(staticIcon)))
|
||||
}
|
||||
|
||||
background =
|
||||
MaterialShapeDrawable().apply {
|
||||
fillColor = context.getColorStateListSafe(R.color.sel_cover_bg)
|
||||
}
|
||||
}
|
||||
|
||||
/** Bind the album cover for a [song]. */
|
||||
open fun bind(song: Song) = loadImpl(song, R.drawable.ic_song)
|
||||
|
||||
/** Bind the album cover for an [album]. */
|
||||
open fun bind(album: Album) = loadImpl(album, R.drawable.ic_album)
|
||||
|
||||
/** Bind the image for an [artist] */
|
||||
open fun bind(artist: Artist) = loadImpl(artist, R.drawable.ic_artist)
|
||||
|
||||
/** Bind the image for a [genre] */
|
||||
open fun bind(genre: Genre) = loadImpl(genre, R.drawable.ic_genre)
|
||||
|
||||
private fun <T : Music> loadImpl(music: T, @DrawableRes error: Int) {
|
||||
if (staticIcon > -1) {
|
||||
throw IllegalStateException("Static StyledImageViews cannot bind new images")
|
||||
}
|
||||
|
||||
dispose()
|
||||
load(music) {
|
||||
error(StyledDrawable(context, error))
|
||||
transformations(SquareFrameTransform.INSTANCE)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A companion drawable that can be used with the style that [StyledImageView] provides.
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
class StyledDrawable(context: Context, private val src: Drawable) : Drawable() {
|
||||
constructor(
|
||||
context: Context,
|
||||
@DrawableRes res: Int
|
||||
) : this(context, context.getDrawableSafe(res))
|
||||
|
||||
init {
|
||||
// Re-tint the drawable to something that will play along with the background
|
||||
DrawableCompat.setTintList(src, context.getColorStateListSafe(R.color.sel_on_cover_bg))
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
src.bounds.set(canvas.clipBounds)
|
||||
val adjustWidth = src.bounds.width() / 4
|
||||
val adjustHeight = src.bounds.height() / 4
|
||||
src.bounds.set(
|
||||
adjustWidth,
|
||||
adjustHeight,
|
||||
src.bounds.width() - adjustWidth,
|
||||
src.bounds.height() - adjustHeight)
|
||||
src.draw(canvas)
|
||||
}
|
||||
|
||||
override fun setAlpha(alpha: Int) {
|
||||
src.alpha = alpha
|
||||
}
|
||||
|
||||
override fun setColorFilter(colorFilter: ColorFilter?) {
|
||||
src.colorFilter = colorFilter
|
||||
}
|
||||
|
||||
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
|
||||
}
|
||||
}
|
|
@ -29,7 +29,6 @@ import org.oxycblt.auxio.music.Album
|
|||
import org.oxycblt.auxio.music.Artist
|
||||
import org.oxycblt.auxio.music.Genre
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.util.getColorStateListSafe
|
||||
import org.oxycblt.auxio.util.getDrawableSafe
|
||||
|
||||
|
@ -52,13 +51,9 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
FrameLayout(context, attrs, defStyleAttr) {
|
||||
private val cornerRadius: Float
|
||||
|
||||
private val inner = BaseStyledImageView(context, attrs)
|
||||
private val inner: StyledImageView
|
||||
private var customView: View? = null
|
||||
private val indicator =
|
||||
BaseStyledImageView(context).apply {
|
||||
setImageDrawable(
|
||||
StyledDrawable(context, context.getDrawableSafe(R.drawable.ic_equalizer)))
|
||||
}
|
||||
private val indicator: StyledImageView
|
||||
|
||||
init {
|
||||
// Android wants you to make separate attributes for each view type, but will
|
||||
|
@ -68,23 +63,14 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
cornerRadius = styledAttrs.getDimension(R.styleable.StyledImageView_cornerRadius, 0f)
|
||||
styledAttrs.recycle()
|
||||
|
||||
inner = StyledImageView(context, attrs)
|
||||
indicator =
|
||||
StyledImageView(context).apply {
|
||||
cornerRadius = this@ImageGroup.cornerRadius
|
||||
staticIcon = context.getDrawableSafe(R.drawable.ic_equalizer)
|
||||
}
|
||||
|
||||
addView(inner)
|
||||
|
||||
// Use clipToOutline and a background drawable to crop images. While Coil's transformation
|
||||
// could theoretically be used to round corners, the corner radius is dependent on the
|
||||
// dimensions of the image, which will result in inconsistent corners across different
|
||||
// album covers unless we resize all covers to be the same size. clipToOutline is both
|
||||
// cheaper and more elegant. As a side-note, this also allows us to re-use the same
|
||||
// background for both the tonal background color and the corner rounding.
|
||||
background = MaterialShapeDrawable()
|
||||
clipToOutline = true
|
||||
|
||||
if (!isInEditMode) {
|
||||
val settingsManager = SettingsManager.getInstance()
|
||||
if (settingsManager.roundCovers) {
|
||||
(background as MaterialShapeDrawable).setCornerSize(cornerRadius)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFinishInflate() {
|
||||
|
@ -99,6 +85,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
background =
|
||||
MaterialShapeDrawable().apply {
|
||||
fillColor = context.getColorStateListSafe(R.color.sel_cover_bg)
|
||||
setCornerSize(cornerRadius)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,16 +18,25 @@
|
|||
package org.oxycblt.auxio.image
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.AttributeSet
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import coil.dispose
|
||||
import coil.load
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.music.Album
|
||||
import org.oxycblt.auxio.music.Artist
|
||||
import org.oxycblt.auxio.music.Genre
|
||||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.util.getColorStateListSafe
|
||||
import org.oxycblt.auxio.util.getDrawableSafe
|
||||
|
||||
/**
|
||||
* An [AppCompatImageView] that applies many of the stylistic choices that Auxio uses regarding
|
||||
|
@ -41,14 +50,27 @@ import org.oxycblt.auxio.settings.SettingsManager
|
|||
class StyledImageView
|
||||
@JvmOverloads
|
||||
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
||||
BaseStyledImageView(context, attrs, defStyleAttr) {
|
||||
private var cornerRadius = 0f
|
||||
AppCompatImageView(context, attrs, defStyleAttr) {
|
||||
var cornerRadius = 0f
|
||||
set(value) {
|
||||
field = value
|
||||
(background as? MaterialShapeDrawable)?.let { bg ->
|
||||
if (!isInEditMode && SettingsManager.getInstance().roundCovers) {
|
||||
bg.setCornerSize(value)
|
||||
} else {
|
||||
bg.setCornerSize(0f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var staticIcon: Drawable? = null
|
||||
set(value) {
|
||||
val wrapped = value?.let { StyledDrawable(context, it) }
|
||||
field = wrapped
|
||||
setImageDrawable(field)
|
||||
}
|
||||
|
||||
init {
|
||||
val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.StyledImageView)
|
||||
cornerRadius = styledAttrs.getDimension(R.styleable.StyledImageView_cornerRadius, 0f)
|
||||
styledAttrs.recycle()
|
||||
|
||||
// Use clipToOutline and a background drawable to crop images. While Coil's transformation
|
||||
// could theoretically be used to round corners, the corner radius is dependent on the
|
||||
// dimensions of the image, which will result in inconsistent corners across different
|
||||
|
@ -56,36 +78,47 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
// cheaper and more elegant. As a side-note, this also allows us to re-use the same
|
||||
// background for both the tonal background color and the corner rounding.
|
||||
clipToOutline = true
|
||||
|
||||
if (!isInEditMode) {
|
||||
val settingsManager = SettingsManager.getInstance()
|
||||
if (settingsManager.roundCovers) {
|
||||
(background as MaterialShapeDrawable).setCornerSize(cornerRadius)
|
||||
}
|
||||
}
|
||||
background =
|
||||
MaterialShapeDrawable().apply {
|
||||
fillColor = context.getColorStateListSafe(R.color.sel_cover_bg)
|
||||
setCornerSize(cornerRadius)
|
||||
}
|
||||
|
||||
override fun bind(song: Song) {
|
||||
super.bind(song)
|
||||
contentDescription =
|
||||
context.getString(R.string.desc_album_cover, song.album.resolveName(context))
|
||||
val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.StyledImageView)
|
||||
val staticIconRes =
|
||||
styledAttrs.getResourceId(
|
||||
R.styleable.StyledImageView_staticIcon, ResourcesCompat.ID_NULL)
|
||||
if (staticIconRes != ResourcesCompat.ID_NULL) {
|
||||
staticIcon = context.getDrawableSafe(staticIconRes)
|
||||
}
|
||||
|
||||
override fun bind(album: Album) {
|
||||
super.bind(album)
|
||||
contentDescription =
|
||||
context.getString(R.string.desc_album_cover, album.resolveName(context))
|
||||
cornerRadius = styledAttrs.getDimension(R.styleable.StyledImageView_cornerRadius, 0f)
|
||||
styledAttrs.recycle()
|
||||
}
|
||||
|
||||
override fun bind(artist: Artist) {
|
||||
super.bind(artist)
|
||||
contentDescription =
|
||||
context.getString(R.string.desc_artist_image, artist.resolveName(context))
|
||||
/** Bind the album cover for a [song]. */
|
||||
fun bind(song: Song) = loadImpl(song, R.drawable.ic_song, R.string.desc_album_cover)
|
||||
|
||||
/** Bind the album cover for an [album]. */
|
||||
fun bind(album: Album) = loadImpl(album, R.drawable.ic_album, R.string.desc_album_cover)
|
||||
|
||||
/** Bind the image for an [artist] */
|
||||
fun bind(artist: Artist) = loadImpl(artist, R.drawable.ic_artist, R.string.desc_artist_image)
|
||||
|
||||
/** Bind the image for a [genre] */
|
||||
fun bind(genre: Genre) = loadImpl(genre, R.drawable.ic_genre, R.string.desc_genre_image)
|
||||
|
||||
private fun <T : Music> loadImpl(music: T, @DrawableRes error: Int, @StringRes desc: Int) {
|
||||
if (staticIcon != null) {
|
||||
error("Static StyledImageViews cannot bind new images")
|
||||
}
|
||||
|
||||
override fun bind(genre: Genre) {
|
||||
super.bind(genre)
|
||||
contentDescription =
|
||||
context.getString(R.string.desc_genre_image, genre.resolveName(context))
|
||||
contentDescription = context.getString(desc, music.resolveName(context))
|
||||
|
||||
dispose()
|
||||
load(music) {
|
||||
error(StyledDrawable(context, context.getDrawableSafe(error)))
|
||||
transformations(SquareFrameTransform.INSTANCE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue