Files
Radio/app/src/main/java/com/michatec/radio/helpers/ImageHelper.kt
Michatec 5ca2b9b7ef - Some Bug fixes
- Remove Code berg icon
- Updated README.md
- Updated AndroidManifest.xml
- Updated Dependencies
- Fix some code
2026-02-24 14:46:03 +01:00

259 lines
8.2 KiB
Kotlin

/*
* ImageHelper.kt
* Implements the ImageHelper object
* An ImageHelper provides helper methods for image related operations
*
* This file is part of
* TRANSISTOR - Radio App for Android
*
* Copyright (c) 2015-22 - Y20K.org
* Licensed under the MIT-License
* http://opensource.org/licenses/MIT
*/
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
}
}