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
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that we don't include the functionally useless
|
|
||||||
// "audio/raw" mime type
|
|
||||||
MimeType(song.mimeType.fromExtension, formatMimeType)
|
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.Artist
|
||||||
import org.oxycblt.auxio.music.Genre
|
import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.settings.SettingsManager
|
|
||||||
import org.oxycblt.auxio.util.getColorStateListSafe
|
import org.oxycblt.auxio.util.getColorStateListSafe
|
||||||
import org.oxycblt.auxio.util.getDrawableSafe
|
import org.oxycblt.auxio.util.getDrawableSafe
|
||||||
|
|
||||||
|
@ -52,13 +51,9 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
FrameLayout(context, attrs, defStyleAttr) {
|
FrameLayout(context, attrs, defStyleAttr) {
|
||||||
private val cornerRadius: Float
|
private val cornerRadius: Float
|
||||||
|
|
||||||
private val inner = BaseStyledImageView(context, attrs)
|
private val inner: StyledImageView
|
||||||
private var customView: View? = null
|
private var customView: View? = null
|
||||||
private val indicator =
|
private val indicator: StyledImageView
|
||||||
BaseStyledImageView(context).apply {
|
|
||||||
setImageDrawable(
|
|
||||||
StyledDrawable(context, context.getDrawableSafe(R.drawable.ic_equalizer)))
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Android wants you to make separate attributes for each view type, but will
|
// 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)
|
cornerRadius = styledAttrs.getDimension(R.styleable.StyledImageView_cornerRadius, 0f)
|
||||||
styledAttrs.recycle()
|
styledAttrs.recycle()
|
||||||
|
|
||||||
|
inner = StyledImageView(context, attrs)
|
||||||
|
indicator =
|
||||||
|
StyledImageView(context).apply {
|
||||||
|
cornerRadius = this@ImageGroup.cornerRadius
|
||||||
|
staticIcon = context.getDrawableSafe(R.drawable.ic_equalizer)
|
||||||
|
}
|
||||||
|
|
||||||
addView(inner)
|
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() {
|
override fun onFinishInflate() {
|
||||||
|
@ -99,6 +85,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
background =
|
background =
|
||||||
MaterialShapeDrawable().apply {
|
MaterialShapeDrawable().apply {
|
||||||
fillColor = context.getColorStateListSafe(R.color.sel_cover_bg)
|
fillColor = context.getColorStateListSafe(R.color.sel_cover_bg)
|
||||||
|
setCornerSize(cornerRadius)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,16 +18,25 @@
|
||||||
package org.oxycblt.auxio.image
|
package org.oxycblt.auxio.image
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import androidx.annotation.AttrRes
|
import androidx.annotation.AttrRes
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.annotation.StringRes
|
||||||
import androidx.appcompat.widget.AppCompatImageView
|
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 com.google.android.material.shape.MaterialShapeDrawable
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
import org.oxycblt.auxio.music.Artist
|
import org.oxycblt.auxio.music.Artist
|
||||||
import org.oxycblt.auxio.music.Genre
|
import org.oxycblt.auxio.music.Genre
|
||||||
|
import org.oxycblt.auxio.music.Music
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.settings.SettingsManager
|
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
|
* 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
|
class StyledImageView
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
||||||
BaseStyledImageView(context, attrs, defStyleAttr) {
|
AppCompatImageView(context, attrs, defStyleAttr) {
|
||||||
private var cornerRadius = 0f
|
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 {
|
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
|
// 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
|
// 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
|
// 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
|
// 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 for both the tonal background color and the corner rounding.
|
||||||
clipToOutline = true
|
clipToOutline = true
|
||||||
|
background =
|
||||||
if (!isInEditMode) {
|
MaterialShapeDrawable().apply {
|
||||||
val settingsManager = SettingsManager.getInstance()
|
fillColor = context.getColorStateListSafe(R.color.sel_cover_bg)
|
||||||
if (settingsManager.roundCovers) {
|
setCornerSize(cornerRadius)
|
||||||
(background as MaterialShapeDrawable).setCornerSize(cornerRadius)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bind(song: Song) {
|
val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.StyledImageView)
|
||||||
super.bind(song)
|
val staticIconRes =
|
||||||
contentDescription =
|
styledAttrs.getResourceId(
|
||||||
context.getString(R.string.desc_album_cover, song.album.resolveName(context))
|
R.styleable.StyledImageView_staticIcon, ResourcesCompat.ID_NULL)
|
||||||
|
if (staticIconRes != ResourcesCompat.ID_NULL) {
|
||||||
|
staticIcon = context.getDrawableSafe(staticIconRes)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bind(album: Album) {
|
cornerRadius = styledAttrs.getDimension(R.styleable.StyledImageView_cornerRadius, 0f)
|
||||||
super.bind(album)
|
styledAttrs.recycle()
|
||||||
contentDescription =
|
|
||||||
context.getString(R.string.desc_album_cover, album.resolveName(context))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bind(artist: Artist) {
|
/** Bind the album cover for a [song]. */
|
||||||
super.bind(artist)
|
fun bind(song: Song) = loadImpl(song, R.drawable.ic_song, R.string.desc_album_cover)
|
||||||
contentDescription =
|
|
||||||
context.getString(R.string.desc_artist_image, artist.resolveName(context))
|
/** 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) {
|
contentDescription = context.getString(desc, music.resolveName(context))
|
||||||
super.bind(genre)
|
|
||||||
contentDescription =
|
dispose()
|
||||||
context.getString(R.string.desc_genre_image, genre.resolveName(context))
|
load(music) {
|
||||||
|
error(StyledDrawable(context, context.getDrawableSafe(error)))
|
||||||
|
transformations(SquareFrameTransform.INSTANCE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue