feat(ui): increase cover art size to 600px and use CatmullRom scaling
Increased the UI cover art request size from 300px to 600px for sharper images on high-DPI displays. Replaced BiLinear with CatmullRom (bicubic) interpolation for higher quality image resizing. Extracted the hardcoded size into a COVER_ART_SIZE constant in the frontend and consolidated backend sizes into a CacheWarmerImageSizes slice. Removed the unused UIThumbnailSize constant. Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
@@ -18,6 +18,7 @@ import {
|
||||
useTranslate,
|
||||
} from 'react-admin'
|
||||
import Lightbox from 'react-image-lightbox'
|
||||
import { COVER_ART_SIZE } from '../consts'
|
||||
import 'react-image-lightbox/style.css'
|
||||
import subsonic from '../subsonic'
|
||||
import {
|
||||
@@ -254,7 +255,7 @@ const AlbumDetails = (props) => {
|
||||
})
|
||||
}, [record])
|
||||
|
||||
const imageUrl = subsonic.getCoverArtUrl(record, 300)
|
||||
const imageUrl = subsonic.getCoverArtUrl(record, COVER_ART_SIZE)
|
||||
const fullImageUrl = subsonic.getCoverArtUrl(record)
|
||||
|
||||
return (
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
ArtistLinkField,
|
||||
OverflowTooltip,
|
||||
} from '../common'
|
||||
import { DraggableTypes } from '../consts'
|
||||
import { COVER_ART_SIZE, DraggableTypes } from '../consts'
|
||||
import clsx from 'clsx'
|
||||
import { AlbumDatesField } from './AlbumDatesField.jsx'
|
||||
|
||||
@@ -157,7 +157,7 @@ const Cover = withContentRect('bounds')(({
|
||||
<div ref={dragAlbumRef}>
|
||||
<img
|
||||
key={record.id} // Force re-render when record changes
|
||||
src={subsonic.getCoverArtUrl(record, 300, true)}
|
||||
src={subsonic.getCoverArtUrl(record, COVER_ART_SIZE, true)}
|
||||
alt={record.name}
|
||||
className={`${classes.cover} ${imageLoading ? classes.coverLoading : ''}`}
|
||||
onLoad={handleImageLoad}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
import Lightbox from 'react-image-lightbox'
|
||||
import ExpandInfoDialog from '../dialogs/ExpandInfoDialog'
|
||||
import AlbumInfo from '../album/AlbumInfo'
|
||||
import { COVER_ART_SIZE } from '../consts'
|
||||
import subsonic from '../subsonic'
|
||||
import { SafeHTML } from '../common/SafeHTML'
|
||||
|
||||
@@ -109,7 +110,7 @@ const DesktopArtistDetails = ({ artistInfo, record, biography }) => {
|
||||
<CardMedia
|
||||
key={record.id}
|
||||
component="img"
|
||||
src={subsonic.getCoverArtUrl(record, 300)}
|
||||
src={subsonic.getCoverArtUrl(record, COVER_ART_SIZE)}
|
||||
className={`${classes.cover} ${imageLoading ? classes.coverLoading : ''}`}
|
||||
onClick={handleOpenLightbox}
|
||||
onLoad={handleImageLoad}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
useImageLoadingState,
|
||||
} from '../common'
|
||||
import Lightbox from 'react-image-lightbox'
|
||||
import { COVER_ART_SIZE } from '../consts'
|
||||
import subsonic from '../subsonic'
|
||||
import { SafeHTML } from '../common/SafeHTML'
|
||||
|
||||
@@ -112,7 +113,7 @@ const MobileArtistDetails = ({ artistInfo, biography, record }) => {
|
||||
<CardMedia
|
||||
key={record.id}
|
||||
component="img"
|
||||
src={subsonic.getCoverArtUrl(record, 300)}
|
||||
src={subsonic.getCoverArtUrl(record, COVER_ART_SIZE)}
|
||||
className={`${classes.cover} ${imageLoading ? classes.coverLoading : ''}`}
|
||||
onClick={handleOpenLightbox}
|
||||
onLoad={handleImageLoad}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useRecordContext } from 'react-admin'
|
||||
import { Avatar } from '@material-ui/core'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import clsx from 'clsx'
|
||||
import { COVER_ART_SIZE } from '../consts'
|
||||
import subsonic from '../subsonic'
|
||||
|
||||
const useStyles = makeStyles({
|
||||
@@ -25,7 +26,7 @@ export const CoverArtAvatar = ({
|
||||
const square = variant !== 'circular'
|
||||
return (
|
||||
<Avatar
|
||||
src={subsonic.getCoverArtUrl(record, 80, square)}
|
||||
src={subsonic.getCoverArtUrl(record, COVER_ART_SIZE, square)}
|
||||
variant={variant}
|
||||
className={clsx(classes.avatar, square && classes.square)}
|
||||
alt={record.name}
|
||||
|
||||
@@ -26,6 +26,8 @@ DraggableTypes.ALL.push(
|
||||
|
||||
export const RADIO_PLACEHOLDER_IMAGE = 'internet-radio-icon.svg'
|
||||
|
||||
export const COVER_ART_SIZE = 600
|
||||
|
||||
export const DEFAULT_SHARE_BITRATE = 128
|
||||
|
||||
export const BITRATE_CHOICES = [
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
OverflowTooltip,
|
||||
useImageLoadingState,
|
||||
} from '../common'
|
||||
import { COVER_ART_SIZE } from '../consts'
|
||||
import subsonic from '../subsonic'
|
||||
|
||||
const useStyles = makeStyles(
|
||||
@@ -106,7 +107,7 @@ const PlaylistDetails = (props) => {
|
||||
handleCloseLightbox,
|
||||
} = useImageLoadingState(record.id)
|
||||
|
||||
const imageUrl = subsonic.getCoverArtUrl(record, 300, true)
|
||||
const imageUrl = subsonic.getCoverArtUrl(record, COVER_ART_SIZE, true)
|
||||
const fullImageUrl = subsonic.getCoverArtUrl(record)
|
||||
|
||||
return (
|
||||
|
||||
@@ -11,7 +11,7 @@ import { makeStyles } from '@material-ui/core/styles'
|
||||
import { urlValidate } from '../utils/validations'
|
||||
import { Title, ImageUploadOverlay, useImageLoadingState } from '../common'
|
||||
import subsonic from '../subsonic'
|
||||
import { RADIO_PLACEHOLDER_IMAGE } from '../consts'
|
||||
import { COVER_ART_SIZE, RADIO_PLACEHOLDER_IMAGE } from '../consts'
|
||||
|
||||
const useStyles = makeStyles({
|
||||
coverParent: {
|
||||
@@ -83,7 +83,7 @@ const RadioCoverArt = ({ record }) => {
|
||||
{record.uploadedImage ? (
|
||||
<CardMedia
|
||||
component="img"
|
||||
src={subsonic.getCoverArtUrl(record, 300, true)}
|
||||
src={subsonic.getCoverArtUrl(record, COVER_ART_SIZE, true)}
|
||||
className={`${classes.cover} ${imageLoading ? classes.coverLoading : ''}`}
|
||||
onLoad={handleImageLoad}
|
||||
onError={handleImageError}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import subsonic from '../subsonic'
|
||||
import { RADIO_PLACEHOLDER_IMAGE } from '../consts'
|
||||
import { COVER_ART_SIZE, RADIO_PLACEHOLDER_IMAGE } from '../consts'
|
||||
|
||||
export async function songFromRadio(radio) {
|
||||
if (!radio) {
|
||||
@@ -8,7 +8,7 @@ export async function songFromRadio(radio) {
|
||||
|
||||
let cover = RADIO_PLACEHOLDER_IMAGE
|
||||
if (radio.uploadedImage) {
|
||||
cover = subsonic.getCoverArtUrl(radio, 300, true)
|
||||
cover = subsonic.getCoverArtUrl(radio, COVER_ART_SIZE, true)
|
||||
} else {
|
||||
// Try favicon as fallback
|
||||
try {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { vi } from 'vitest'
|
||||
import { COVER_ART_SIZE } from '../consts'
|
||||
import subsonic from './index'
|
||||
|
||||
describe('getCoverArtUrl', () => {
|
||||
@@ -30,10 +31,10 @@ describe('getCoverArtUrl', () => {
|
||||
updatedAt: '2023-01-01T00:00:00Z',
|
||||
}
|
||||
|
||||
const url = subsonic.getCoverArtUrl(playlistRecord, 300, true)
|
||||
const url = subsonic.getCoverArtUrl(playlistRecord, COVER_ART_SIZE, true)
|
||||
|
||||
expect(url).toContain('pl-playlist-123')
|
||||
expect(url).toContain('size=300')
|
||||
expect(url).toContain('size=600')
|
||||
expect(url).toContain('square=true')
|
||||
expect(url).toContain('_=2023-01-01T00%3A00%3A00Z')
|
||||
})
|
||||
@@ -44,10 +45,10 @@ describe('getCoverArtUrl', () => {
|
||||
sync: true,
|
||||
}
|
||||
|
||||
const url = subsonic.getCoverArtUrl(playlistRecord, 300, true)
|
||||
const url = subsonic.getCoverArtUrl(playlistRecord, COVER_ART_SIZE, true)
|
||||
|
||||
expect(url).toContain('pl-playlist-123')
|
||||
expect(url).toContain('size=300')
|
||||
expect(url).toContain('size=600')
|
||||
expect(url).toContain('square=true')
|
||||
expect(url).not.toContain('_=')
|
||||
})
|
||||
@@ -59,10 +60,10 @@ describe('getCoverArtUrl', () => {
|
||||
updatedAt: '2023-01-01T00:00:00Z',
|
||||
}
|
||||
|
||||
const url = subsonic.getCoverArtUrl(albumRecord, 300, true)
|
||||
const url = subsonic.getCoverArtUrl(albumRecord, COVER_ART_SIZE, true)
|
||||
|
||||
expect(url).toContain('al-album-123')
|
||||
expect(url).toContain('size=300')
|
||||
expect(url).toContain('size=600')
|
||||
expect(url).toContain('square=true')
|
||||
})
|
||||
|
||||
@@ -73,10 +74,10 @@ describe('getCoverArtUrl', () => {
|
||||
updatedAt: '2023-01-01T00:00:00Z',
|
||||
}
|
||||
|
||||
const url = subsonic.getCoverArtUrl(songRecord, 300, true)
|
||||
const url = subsonic.getCoverArtUrl(songRecord, COVER_ART_SIZE, true)
|
||||
|
||||
expect(url).toContain('mf-song-123')
|
||||
expect(url).toContain('size=300')
|
||||
expect(url).toContain('size=600')
|
||||
expect(url).toContain('square=true')
|
||||
})
|
||||
|
||||
@@ -86,10 +87,10 @@ describe('getCoverArtUrl', () => {
|
||||
updatedAt: '2023-01-01T00:00:00Z',
|
||||
}
|
||||
|
||||
const url = subsonic.getCoverArtUrl(artistRecord, 300, true)
|
||||
const url = subsonic.getCoverArtUrl(artistRecord, COVER_ART_SIZE, true)
|
||||
|
||||
expect(url).toContain('ar-artist-123')
|
||||
expect(url).toContain('size=300')
|
||||
expect(url).toContain('size=600')
|
||||
expect(url).toContain('square=true')
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user