mirror of
https://github.com/Michatec/Radio.git
synced 2026-03-31 23:46:28 +02:00
245 lines
7.9 KiB
Kotlin
245 lines
7.9 KiB
Kotlin
package com.michatec.radio.helpers
|
|
|
|
import android.content.Context
|
|
import android.graphics.*
|
|
import android.net.Uri
|
|
import androidx.core.content.ContextCompat
|
|
import androidx.core.graphics.drawable.toBitmap
|
|
import androidx.core.net.toUri
|
|
import androidx.palette.graphics.Palette
|
|
import com.michatec.radio.R
|
|
import java.io.ByteArrayOutputStream
|
|
import java.io.IOException
|
|
import java.io.InputStream
|
|
import androidx.core.graphics.createBitmap
|
|
|
|
|
|
/*
|
|
* ImageHelper class
|
|
*/
|
|
object ImageHelper {
|
|
|
|
/* Get scaling factor from display density */
|
|
fun getDensityScalingFactor(context: Context): Float {
|
|
return context.resources.displayMetrics.density
|
|
}
|
|
|
|
|
|
/* Get a scaled version of the station image */
|
|
fun getScaledStationImage(context: Context, imageUri: Uri, imageSize: Int): Bitmap {
|
|
val size: Int = (imageSize * getDensityScalingFactor(context)).toInt()
|
|
return decodeSampledBitmapFromUri(context, imageUri, size, size)
|
|
}
|
|
|
|
|
|
/* Get an unscaled version of the station image */
|
|
fun getStationImage(context: Context, imageUriString: String): Bitmap {
|
|
var bitmap: Bitmap? = null
|
|
|
|
if (imageUriString.isNotEmpty()) {
|
|
try {
|
|
// just decode the file
|
|
bitmap = BitmapFactory.decodeFile(imageUriString.toUri().path)
|
|
} catch (e: Exception) {
|
|
e.printStackTrace()
|
|
}
|
|
}
|
|
|
|
// get default image
|
|
if (bitmap == null) {
|
|
bitmap = ContextCompat.getDrawable(context, R.drawable.ic_default_station_image_72dp)!!
|
|
.toBitmap()
|
|
}
|
|
|
|
return bitmap
|
|
}
|
|
|
|
|
|
/* Get an unscaled version of the station image as a ByteArray */
|
|
fun getStationImageAsByteArray(context: Context, imageUriString: String = String()): ByteArray {
|
|
val coverBitmap: Bitmap = getStationImage(context, imageUriString)
|
|
val stream = ByteArrayOutputStream()
|
|
coverBitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)
|
|
val coverByteArray: ByteArray = stream.toByteArray()
|
|
coverBitmap.recycle()
|
|
return coverByteArray
|
|
}
|
|
|
|
|
|
/* Creates station image on a square background with the main station image color and option padding for adaptive icons */
|
|
fun createSquareImage(
|
|
context: Context,
|
|
bitmap: Bitmap,
|
|
backgroundColor: Int,
|
|
size: Int,
|
|
adaptivePadding: Boolean
|
|
): Bitmap {
|
|
|
|
// create background
|
|
val background = Paint()
|
|
background.style = Paint.Style.FILL
|
|
if (backgroundColor != -1) {
|
|
background.color = backgroundColor
|
|
} else {
|
|
background.color = ContextCompat.getColor(context, R.color.default_neutral_dark)
|
|
}
|
|
|
|
// create empty bitmap and canvas
|
|
val outputImage: Bitmap = createBitmap(size, size, Bitmap.Config.ARGB_8888)
|
|
val imageCanvas = Canvas(outputImage)
|
|
|
|
// draw square background
|
|
val right = size.toFloat()
|
|
val bottom = size.toFloat()
|
|
imageCanvas.drawRect(0f, 0f, right, bottom, background)
|
|
|
|
// draw input image onto canvas using transformation matrix
|
|
val paint = Paint()
|
|
paint.isFilterBitmap = true
|
|
imageCanvas.drawBitmap(
|
|
bitmap,
|
|
createTransformationMatrix(
|
|
size,
|
|
bitmap.height.toFloat(),
|
|
bitmap.width.toFloat(),
|
|
adaptivePadding
|
|
),
|
|
paint
|
|
)
|
|
return outputImage
|
|
}
|
|
|
|
|
|
/* Extracts color from an image */
|
|
fun getMainColor(context: Context, imageUri: Uri): Int {
|
|
// extract color palette from station image
|
|
val palette: Palette =
|
|
Palette.from(decodeSampledBitmapFromUri(context, imageUri, 72, 72)).generate()
|
|
// get muted and vibrant swatches
|
|
val vibrantSwatch = palette.vibrantSwatch
|
|
val mutedSwatch = palette.mutedSwatch
|
|
|
|
when {
|
|
vibrantSwatch != null -> {
|
|
// return vibrant color
|
|
val rgb = vibrantSwatch.rgb
|
|
return Color.argb(255, Color.red(rgb), Color.green(rgb), Color.blue(rgb))
|
|
}
|
|
mutedSwatch != null -> {
|
|
// return muted color
|
|
val rgb = mutedSwatch.rgb
|
|
return Color.argb(255, Color.red(rgb), Color.green(rgb), Color.blue(rgb))
|
|
}
|
|
else -> {
|
|
// default return
|
|
return context.resources.getColor(R.color.default_neutral_medium_light, null)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Return sampled down image for given Uri */
|
|
private fun decodeSampledBitmapFromUri(
|
|
context: Context,
|
|
imageUri: Uri,
|
|
reqWidth: Int,
|
|
reqHeight: Int
|
|
): Bitmap {
|
|
var bitmap: Bitmap? = null
|
|
if (imageUri.toString().isNotEmpty()) {
|
|
try {
|
|
// first decode with inJustDecodeBounds=true to check dimensions
|
|
var stream: InputStream? = context.contentResolver.openInputStream(imageUri)
|
|
val options = BitmapFactory.Options()
|
|
options.inJustDecodeBounds = true
|
|
BitmapFactory.decodeStream(stream, null, options)
|
|
stream?.close()
|
|
|
|
// calculate inSampleSize
|
|
options.inSampleSize = calculateSampleParameter(options, reqWidth, reqHeight)
|
|
|
|
// decode bitmap with inSampleSize set
|
|
stream = context.contentResolver.openInputStream(imageUri)
|
|
options.inJustDecodeBounds = false
|
|
bitmap = BitmapFactory.decodeStream(stream, null, options)
|
|
stream?.close()
|
|
} catch (e: IOException) {
|
|
e.printStackTrace()
|
|
}
|
|
}
|
|
|
|
// get default image
|
|
if (bitmap == null) {
|
|
bitmap = ContextCompat.getDrawable(context, R.drawable.ic_default_station_image_72dp)!!
|
|
.toBitmap()
|
|
}
|
|
|
|
return bitmap
|
|
}
|
|
|
|
|
|
/* Calculates parameter needed to scale image down */
|
|
private fun calculateSampleParameter(
|
|
options: BitmapFactory.Options,
|
|
reqWidth: Int,
|
|
reqHeight: Int
|
|
): Int {
|
|
// get size of original image
|
|
val height = options.outHeight
|
|
val width = options.outWidth
|
|
var inSampleSize = 1
|
|
|
|
if (height > reqHeight || width > reqWidth) {
|
|
|
|
val halfHeight = height / 2
|
|
val halfWidth = width / 2
|
|
|
|
// calculates the largest inSampleSize value that is a power of 2 and keeps both
|
|
// height and width larger than the requested height and width
|
|
while (halfHeight / inSampleSize > reqHeight && halfWidth / inSampleSize > reqWidth) {
|
|
inSampleSize *= 2
|
|
}
|
|
}
|
|
return inSampleSize
|
|
}
|
|
|
|
|
|
/* Creates a transformation matrix with the given size and optional padding */
|
|
private fun createTransformationMatrix(
|
|
size: Int,
|
|
inputImageHeight: Float,
|
|
inputImageWidth: Float,
|
|
scaled: Boolean
|
|
): Matrix {
|
|
val matrix = Matrix()
|
|
|
|
// calculate padding
|
|
var padding = 0f
|
|
if (scaled) {
|
|
padding = size.toFloat() / 4f
|
|
}
|
|
|
|
// define variables needed for transformation matrix
|
|
val aspectRatio: Float
|
|
val xTranslation: Float
|
|
val yTranslation: Float
|
|
|
|
// landscape format and square
|
|
if (inputImageWidth >= inputImageHeight) {
|
|
aspectRatio = (size - padding * 2) / inputImageWidth
|
|
xTranslation = 0.0f + padding
|
|
yTranslation = (size - inputImageHeight * aspectRatio) / 2.0f
|
|
} else {
|
|
aspectRatio = (size - padding * 2) / inputImageHeight
|
|
yTranslation = 0.0f + padding
|
|
xTranslation = (size - inputImageWidth * aspectRatio) / 2.0f
|
|
}
|
|
|
|
// construct transformation matrix
|
|
matrix.postTranslate(xTranslation, yTranslation)
|
|
matrix.preScale(aspectRatio, aspectRatio)
|
|
return matrix
|
|
}
|
|
|
|
}
|