Android Kotlin 图像处理示例
Android Kotlin 图像处理示例,包括图像读取保存、缩放和格式转换
💻 图像读取和保存 kotlin
🟡 intermediate
⭐⭐⭐
从各种来源读取图像并将其保存到存储
⏱️ 25 min
🏷️ kotlin, android, image processing
Prerequisites:
Intermediate Kotlin, Android SDK
// Android Kotlin Image Read and Save Examples
// Using android.graphics.Bitmap and related classes
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
// 1. Reading Images
class ImageReader(private val context: Context) {
// Read bitmap from resources
fun readFromResource(resourceId: Int): Bitmap? {
return try {
val options = BitmapFactory.Options()
BitmapFactory.decodeResource(context.resources, resourceId, options)
} catch (e: Exception) {
println("Error reading from resource: ${e.message}")
null
}
}
// Read bitmap from file path
fun readFromFile(filePath: String): Bitmap? {
return try {
val file = File(filePath)
if (file.exists()) {
BitmapFactory.decodeFile(filePath)
} else {
println("File not found: $filePath")
null
}
} catch (e: Exception) {
println("Error reading from file: ${e.message}")
null
}
}
// Read bitmap from URI
fun readFromUri(uri: Uri): Bitmap? {
return try {
val inputStream: InputStream = context.contentResolver.openInputStream(uri) ?: return null
BitmapFactory.decodeStream(inputStream)
} catch (e: Exception) {
println("Error reading from URI: ${e.message}")
null
}
}
// Read with sampling (for memory efficiency)
fun readWithSampled(filePath: String, reqWidth: Int, reqHeight: Int): Bitmap? {
return try {
// First decode with inJustDecodeBounds=true to check dimensions
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeFile(filePath, options)
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight)
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false
BitmapFactory.decodeFile(filePath, options)
} catch (e: Exception) {
println("Error reading with sampling: ${e.message}")
null
}
}
// Read from assets
fun readFromAssets(assetPath: String): Bitmap? {
return try {
val inputStream = context.assets.open(assetPath)
BitmapFactory.decodeStream(inputStream)
} catch (e: Exception) {
println("Error reading from assets: ${e.message}")
null
}
}
// Calculate inSampleSize for efficient loading
private fun calculateInSampleSize(
options: BitmapFactory.Options,
reqWidth: Int,
reqHeight: Int
): Int {
val height = options.outHeight
val width = options.outWidth
var inSampleSize = 1
if (height > reqHeight || width > reqWidth) {
val halfHeight = height / 2
val halfWidth = width / 2
while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
inSampleSize *= 2
}
}
return inSampleSize
}
// Read with specific config
fun readWithConfig(filePath: String, config: Bitmap.Config): Bitmap? {
return try {
val options = BitmapFactory.Options()
options.inPreferredConfig = config
BitmapFactory.decodeFile(filePath, options)
} catch (e: Exception) {
println("Error reading with config: ${e.message}")
null
}
}
}
// 2. Saving Images
class ImageSaver(private val context: Context) {
// Save bitmap to file
fun saveToFile(bitmap: Bitmap, filePath: String, format: Bitmap.CompressFormat, quality: Int): Boolean {
return try {
val file = File(filePath)
file.parentFile?.mkdirs()
val outputStream = FileOutputStream(file)
bitmap.compress(format, quality, outputStream)
outputStream.flush()
outputStream.close()
println("Saved to: $filePath")
true
} catch (e: Exception) {
println("Error saving to file: ${e.message}")
false
}
}
// Save to internal storage
fun saveToInternalStorage(bitmap: Bitmap, filename: String, format: Bitmap.CompressFormat): String? {
return try {
val file = File(context.filesDir, filename)
val outputStream = FileOutputStream(file)
bitmap.compress(format, 100, outputStream)
outputStream.flush()
outputStream.close()
file.absolutePath
} catch (e: Exception) {
println("Error saving to internal storage: ${e.message}")
null
}
}
// Save to cache directory
fun saveToCache(bitmap: Bitmap, filename: String, format: Bitmap.CompressFormat): String? {
return try {
val file = File(context.cacheDir, filename)
val outputStream = FileOutputStream(file)
bitmap.compress(format, 100, outputStream)
outputStream.flush()
outputStream.close()
file.absolutePath
} catch (e: Exception) {
println("Error saving to cache: ${e.message}")
null
}
}
// Save to external storage
fun saveToExternalStorage(bitmap: Bitmap, filename: String, format: Bitmap.CompressFormat): String? {
return try {
// Note: Requires WRITE_EXTERNAL_STORAGE permission
val directory = context.getExternalFilesDir(null)
val file = File(directory, filename)
val outputStream = FileOutputStream(file)
bitmap.compress(format, 100, outputStream)
outputStream.flush()
outputStream.close()
file.absolutePath
} catch (e: Exception) {
println("Error saving to external storage: ${e.message}")
null
}
}
// Save with quality setting
fun saveWithQuality(bitmap: Bitmap, filePath: String, quality: Int): Boolean {
val format = when (filePath.substringAfterLast('.', "").lowercase()) {
"png" -> Bitmap.CompressFormat.PNG
"webp" -> Bitmap.CompressFormat.WEBP
else -> Bitmap.CompressFormat.JPEG
}
return saveToFile(bitmap, filePath, format, quality)
}
}
// 3. Image Information
class ImageInfo {
// Get image dimensions
fun getImageDimensions(filePath: String): Pair<Int, Int>? {
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
try {
BitmapFactory.decodeFile(filePath, options)
return Pair(options.outWidth, options.outHeight)
} catch (e: Exception) {
println("Error getting dimensions: ${e.message}")
return null
}
}
// Get image file size
fun getImageFileSize(filePath: String): Long {
val file = File(filePath)
return if (file.exists()) {
file.length()
} else {
0L
}
}
// Get image type from file
fun getImageType(filePath: String): String? {
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
return try {
BitmapFactory.decodeFile(filePath, options)
when (options.outMimeType) {
"image/png" -> "PNG"
"image/jpeg" -> "JPEG"
"image/webp" -> "WebP"
"image/gif" -> "GIF"
else -> "Unknown"
}
} catch (e: Exception) {
println("Error getting image type: ${e.message}")
null
}
}
// Get detailed image info
fun getDetailedInfo(filePath: String): ImageMetadata? {
val file = File(filePath)
if (!file.exists()) return null
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
return try {
BitmapFactory.decodeFile(filePath, options)
ImageMetadata(
width = options.outWidth,
height = options.outHeight,
mimeType = options.outMimeType,
fileSize = file.length(),
path = file.absolutePath
)
} catch (e: Exception) {
println("Error getting detailed info: ${e.message}")
null
}
}
}
// Image Metadata data class
data class ImageMetadata(
val width: Int,
val height: Int,
val mimeType: String?,
val fileSize: Long,
val path: String
)
// 4. Advanced Image Loading
class AdvancedImageLoader(private val context: Context) {
// Load with exact dimensions
fun loadWithExactDimensions(filePath: String, targetWidth: Int, targetHeight: Int): Bitmap? {
return try {
// First get dimensions
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeFile(filePath, options)
// Calculate scale
val scaleWidth = options.outWidth.toFloat() / targetWidth
val scaleHeight = options.outHeight.toFloat() / targetHeight
val scale = maxOf(scaleWidth, scaleHeight)
if (scale > 1) {
options.inSampleSize = scale.toInt()
}
options.inJustDecodeBounds = false
// Decode and scale
val bitmap = BitmapFactory.decodeFile(filePath, options)
Bitmap.createScaledBitmap(bitmap, targetWidth, targetHeight, true)
} catch (e: Exception) {
println("Error loading with dimensions: ${e.message}")
null
}
}
// Load with memory constraints
fun loadWithMemoryLimit(filePath: String, maxMemoryBytes: Long): Bitmap? {
return try {
val options = BitmapFactory.Options()
// Get original dimensions
options.inJustDecodeBounds = true
BitmapFactory.decodeFile(filePath, options)
// Calculate sample size based on memory limit
val bytesPerPixel = 4 // ARGB_8888
val totalPixels = options.outWidth * options.outHeight
val estimatedMemory = totalPixels * bytesPerPixel
var inSampleSize = 1
if (estimatedMemory > maxMemoryBytes) {
inSampleSize = (estimatedMemory / maxMemoryBytes).toInt()
}
options.inJustDecodeBounds = false
options.inSampleSize = inSampleSize
options.inPreferredConfig = Bitmap.Config.RGB_565 // Use less memory
BitmapFactory.decodeFile(filePath, options)
} catch (e: Exception) {
println("Error loading with memory limit: ${e.message}")
null
}
}
}
// 5. Image Validation
class ImageValidator {
// Validate if file is a valid image
fun isValidImage(filePath: String): Boolean {
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
return try {
BitmapFactory.decodeFile(filePath, options)
options.outWidth != -1 && options.outHeight != -1
} catch (e: Exception) {
false
}
}
// Check if image is corrupt
fun isCorrupt(filePath: String): Boolean {
return try {
val bitmap = BitmapFactory.decodeFile(filePath)
bitmap == null
} catch (e: Exception) {
true
}
}
// Validate image dimensions
fun hasValidDimensions(filePath: String, minWidth: Int, minHeight: Int): Boolean {
val info = ImageInfo()
val dimensions = info.getImageDimensions(filePath)
return dimensions != null &&
dimensions.first >= minWidth &&
dimensions.second >= minHeight
}
}
// 6. Batch Image Operations
class BatchImageOperations(private val context: Context) {
// Read multiple images
fun readMultipleImages(filePaths: List<String>): List<Bitmap> {
val reader = ImageReader(context)
val bitmaps = mutableListOf<Bitmap>()
for (path in filePaths) {
reader.readFromFile(path)?.let { bitmaps.add(it) }
}
return bitmaps
}
// Save multiple images
fun saveMultipleImages(bitmaps: List<Bitmap>, directory: String, prefix: String): Boolean {
val saver = ImageSaver(context)
var success = true
bitmaps.forEachIndexed { index, bitmap ->
val filename = "${prefix}_$index.jpg"
val filePath = "$directory/$filename"
if (!saver.saveToFile(bitmap, filePath, Bitmap.CompressFormat.JPEG, 90)) {
success = false
}
}
return success
}
}
// Main demonstration
fun demonstrateImageReadSave(context: Context) {
println("=== Android Kotlin Image Read and Save Examples ===\n")
val imageReader = ImageReader(context)
val imageSaver = ImageSaver(context)
val imageInfo = ImageInfo()
// 1. Reading images
println("--- 1. Reading Images ---")
// Note: These would be actual file paths in real usage
val sampleImagePath = "/sdcard/sample_image.jpg"
println("Reading from file: $sampleImagePath")
val bitmapFromFile = imageReader.readFromFile(sampleImagePath)
println("Bitmap loaded: ${bitmapFromFile != null}")
println("\nReading with sampling (200x200):")
val sampledBitmap = imageReader.readWithSampled(sampleImagePath, 200, 200)
println("Sampled bitmap loaded: ${sampledBitmap != null}")
// 2. Getting image info
println("\n--- 2. Getting Image Info ---")
val dimensions = imageInfo.getImageDimensions(sampleImagePath)
println("Dimensions: ${dimensions?.first} x ${dimensions?.second}")
val fileSize = imageInfo.getImageFileSize(sampleImagePath)
println("File size: $fileSize bytes")
val imageType = imageInfo.getImageType(sampleImagePath)
println("Image type: $imageType")
val detailedInfo = imageInfo.getDetailedInfo(sampleImagePath)
println("\nDetailed info:")
println(" Path: ${detailedInfo?.path}")
println(" Size: ${detailedInfo?.width}x${detailedInfo?.height}")
println(" MIME type: ${detailedInfo?.mimeType}")
// 3. Saving images
println("\n--- 3. Saving Images ---")
bitmapFromFile?.let { bitmap ->
val outputPath = "${context.cacheDir}/saved_image.jpg"
val saved = imageSaver.saveToFile(bitmap, outputPath, Bitmap.CompressFormat.JPEG, 90)
println("Saved to cache: $saved")
val internalPath = imageSaver.saveToInternalStorage(bitmap, "internal_image.jpg", Bitmap.CompressFormat.JPEG)
println("Saved to internal: $internalPath")
val cachePath = imageSaver.saveToCache(bitmap, "cache_image.jpg", Bitmap.CompressFormat.JPEG)
println("Saved to cache dir: $cachePath")
}
// 4. Advanced loading
println("\n--- 4. Advanced Loading ---")
val advancedLoader = AdvancedImageLoader(context)
println("Load with exact dimensions (300x300)")
val exactBitmap = advancedLoader.loadWithExactDimensions(sampleImagePath, 300, 300)
println("Loaded: ${exactBitmap != null}")
println("\nLoad with memory limit (1MB)")
val limitedBitmap = advancedLoader.loadWithMemoryLimit(sampleImagePath, 1024 * 1024)
println("Loaded: ${limitedBitmap != null}")
// 5. Validation
println("\n--- 5. Validation ---")
val validator = ImageValidator()
println("Is valid image: ${validator.isValidImage(sampleImagePath)}")
println("Is corrupt: ${validator.isCorrupt(sampleImagePath)}")
println("Has valid dimensions (>100x100): ${validator.hasValidDimensions(sampleImagePath, 100, 100)}")
println("\n=== All Image Read and Save Examples Completed ===")
}
💻 图像缩放 kotlin
🟡 intermediate
⭐⭐⭐⭐
使用各种算法和质量设置调整图像大小和缩放
⏱️ 30 min
🏷️ kotlin, android, image processing, scaling
Prerequisites:
Intermediate Kotlin, Android Graphics
// Android Kotlin Image Scaling Examples
// Using android.graphics.Bitmap and Matrix
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.Paint
import android.content.Context
import java.io.File
// 1. Basic Scaling
class BasicImageScaler {
// Scale to specific dimensions
fun scaleToSize(bitmap: Bitmap, newWidth: Int, newHeight: Int): Bitmap {
return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true)
}
// Scale by factor
fun scaleByFactor(bitmap: Bitmap, factor: Float): Bitmap {
val newWidth = (bitmap.width * factor).toInt()
val newHeight = (bitmap.height * factor).toInt()
return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true)
}
// Scale maintaining aspect ratio
fun scaleMaintainingAspectRatio(
bitmap: Bitmap,
maxWidth: Int,
maxHeight: Int
): Bitmap {
val widthRatio = maxWidth.toFloat() / bitmap.width
val heightRatio = maxHeight.toFloat() / bitmap.height
val scaleFactor = minOf(widthRatio, heightRatio)
val newWidth = (bitmap.width * scaleFactor).toInt()
val newHeight = (bitmap.height * scaleFactor).toInt()
return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true)
}
// Scale to fit width
fun scaleToWidth(bitmap: Bitmap, targetWidth: Int): Bitmap {
val scale = targetWidth.toFloat() / bitmap.width
val newHeight = (bitmap.height * scale).toInt()
return Bitmap.createScaledBitmap(bitmap, targetWidth, newHeight, true)
}
// Scale to fit height
fun scaleToHeight(bitmap: Bitmap, targetHeight: Int): Bitmap {
val scale = targetHeight.toFloat() / bitmap.height
val newWidth = (bitmap.width * scale).toInt()
return Bitmap.createScaledBitmap(bitmap, newWidth, targetHeight, true)
}
}
// 2. Advanced Scaling with Matrix
class MatrixScaler {
private val paint = Paint().apply {
isFilterBitmap = true
}
// Scale using Matrix
fun scaleWithMatrix(bitmap: Bitmap, scaleX: Float, scaleY: Float): Bitmap {
val matrix = Matrix()
matrix.postScale(scaleX, scaleY)
val scaledBitmap = Bitmap.createBitmap(
(bitmap.width * scaleX).toInt(),
(bitmap.height * scaleY).toInt(),
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(scaledBitmap)
canvas.drawBitmap(bitmap, matrix, paint)
return scaledBitmap
}
// Scale with rotation
fun scaleAndRotate(
bitmap: Bitmap,
scaleX: Float,
scaleY: Float,
rotation: Float
): Bitmap {
val matrix = Matrix()
matrix.postRotate(rotation)
matrix.postScale(scaleX, scaleY)
val scaledBitmap = Bitmap.createBitmap(
bitmap.width,
bitmap.height,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(scaledBitmap)
canvas.drawBitmap(bitmap, matrix, paint)
return scaledBitmap
}
// Scale with pivot point
fun scaleFromPoint(
bitmap: Bitmap,
scaleX: Float,
scaleY: Float,
pivotX: Float,
pivotY: Float
): Bitmap {
val matrix = Matrix()
matrix.setScale(scaleX, scaleY, pivotX, pivotY)
val scaledBitmap = Bitmap.createBitmap(
bitmap.width,
bitmap.height,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(scaledBitmap)
canvas.drawBitmap(bitmap, matrix, paint)
return scaledBitmap
}
}
// 3. High Quality Scaling
class QualityScaler {
// Scale with high quality
fun scaleHighQuality(bitmap: Bitmap, newWidth: Int, newHeight: Int): Bitmap {
val paint = Paint().apply {
isFilterBitmap = true
isAntiAlias = true
isDither = true
}
val scaledBitmap = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(scaledBitmap)
val rect = android.graphics.Rect(0, 0, bitmap.width, bitmap.height)
val destRect = android.graphics.Rect(0, 0, newWidth, newHeight)
canvas.drawBitmap(bitmap, rect, destRect, paint)
return scaledBitmap
}
// Scale with filtering options
fun scaleWithFiltering(
bitmap: Bitmap,
newWidth: Int,
newHeight: Int,
filterBitmap: Boolean
): Bitmap {
val paint = Paint().apply {
isFilterBitmap = filterBitmap
}
return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, filterBitmap)
}
// Smooth scale (multiple passes)
fun smoothScale(bitmap: Bitmap, targetWidth: Int, targetHeight: Int): Bitmap {
var currentBitmap = bitmap
var currentWidth = bitmap.width
var currentHeight = bitmap.height
// Scale in multiple steps for better quality
while (currentWidth > targetWidth * 1.5 || currentHeight > targetHeight * 1.5) {
val scaleFactor = 0.75f
currentWidth = (currentWidth * scaleFactor).toInt()
currentHeight = (currentHeight * scaleFactor).toInt()
currentBitmap = Bitmap.createScaledBitmap(
currentBitmap,
currentWidth,
currentHeight,
true
)
}
// Final scale to exact size
return Bitmap.createScaledBitmap(
currentBitmap,
targetWidth,
targetHeight,
true
)
}
}
// 4. Thumbnail Generation
class ThumbnailGenerator {
// Generate thumbnail
fun generateThumbnail(
bitmap: Bitmap,
thumbWidth: Int,
thumbHeight: Int
): Bitmap {
val scaler = BasicImageScaler()
return scaler.scaleMaintainingAspectRatio(bitmap, thumbWidth, thumbHeight)
}
// Generate square thumbnail (crop center)
fun generateSquareThumbnail(bitmap: Bitmap, size: Int): Bitmap {
val minDimension = minOf(bitmap.width, bitmap.height)
// Calculate crop bounds
val x = (bitmap.width - minDimension) / 2
val y = (bitmap.height - minDimension) / 2
// Crop to square
val cropped = Bitmap.createBitmap(
bitmap,
x, y,
minDimension, minDimension
)
// Scale to target size
return Bitmap.createScaledBitmap(cropped, size, size, true)
}
// Generate thumbnail with rounded corners
fun generateRoundedThumbnail(
bitmap: Bitmap,
width: Int,
height: Int,
cornerRadius: Float
): Bitmap {
// First scale the image
val scaler = BasicImageScaler()
val scaled = scaler.scaleMaintainingAspectRatio(bitmap, width, height)
// Create rounded bitmap
val output = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(output)
val paint = Paint().apply {
isAntiAlias = true
color = android.graphics.Color.BLACK
}
// Draw rounded rectangle
val rect = android.graphics.RectF(0f, 0f, width.toFloat(), height.toFloat())
canvas.drawRoundRect(rect, cornerRadius, cornerRadius, paint)
// Set mask for image
paint.xfermode = android.graphics.PorterDuffXfermode(android.graphics.PorterDuff.Mode.SRC_IN)
canvas.drawBitmap(scaled, 0f, 0f, paint)
return output
}
// Generate circular thumbnail
fun generateCircularThumbnail(bitmap: Bitmap, diameter: Int): Bitmap {
// Create square thumbnail first
val square = generateSquareThumbnail(bitmap, diameter)
// Create circular bitmap
val output = Bitmap.createBitmap(diameter, diameter, Bitmap.Config.ARGB_8888)
val canvas = Canvas(output)
val paint = Paint().apply {
isAntiAlias = true
color = android.graphics.Color.BLACK
}
// Draw circle
val radius = diameter / 2f
canvas.drawCircle(radius, radius, radius, paint)
// Set mask for image
paint.xfermode = android.graphics.PorterDuffXfermode(android.graphics.PorterDuff.Mode.SRC_IN)
canvas.drawBitmap(square, 0f, 0f, paint)
return output
}
}
// 5. Batch Scaling
class BatchScaler(private val context: Context) {
// Scale multiple images
fun scaleMultiple(
filePaths: List<String>,
targetWidth: Int,
targetHeight: Int
): List<Bitmap> {
val scaledBitmaps = mutableListOf<Bitmap>()
for (path in filePaths) {
try {
val bitmap = BitmapFactory.decodeFile(path)
val scaler = BasicImageScaler()
val scaled = scaler.scaleMaintainingAspectRatio(bitmap, targetWidth, targetHeight)
scaledBitmaps.add(scaled)
} catch (e: Exception) {
println("Error scaling $path: ${e.message}")
}
}
return scaledBitmaps
}
// Generate thumbnails for multiple images
fun generateThumbnails(
filePaths: List<String>,
thumbSize: Int
): List<Bitmap> {
val thumbnails = mutableListOf<Bitmap>()
val generator = ThumbnailGenerator()
for (path in filePaths) {
try {
val bitmap = BitmapFactory.decodeFile(path)
val thumb = generator.generateSquareThumbnail(bitmap, thumbSize)
thumbnails.add(thumb)
} catch (e: Exception) {
println("Error generating thumbnail for $path: ${e.message}")
}
}
return thumbnails
}
// Save scaled images
fun saveScaledImages(
filePaths: List<String>,
outputDir: String,
targetWidth: Int,
targetHeight: Int
): Boolean {
var success = true
for ((index, path) in filePaths.withIndex()) {
try {
val bitmap = BitmapFactory.decodeFile(path)
val scaler = BasicImageScaler()
val scaled = scaler.scaleMaintainingAspectRatio(bitmap, targetWidth, targetHeight)
val outputFile = File(outputDir, "scaled_$index.jpg")
outputFile.outputStream().use { outputStream ->
scaled.compress(Bitmap.CompressFormat.JPEG, 90, outputStream)
}
} catch (e: Exception) {
println("Error saving scaled image: ${e.message}")
success = false
}
}
return success
}
}
// 6. Scaling Utilities
class ScalingUtils {
// Calculate target size maintaining aspect ratio
fun calculateTargetSize(
originalWidth: Int,
originalHeight: Int,
maxWidth: Int,
maxHeight: Int
): Pair<Int, Int> {
val widthRatio = maxWidth.toFloat() / originalWidth
val heightRatio = maxHeight.toFloat() / originalHeight
val scaleFactor = minOf(widthRatio, heightRatio)
val newWidth = (originalWidth * scaleFactor).toInt()
val newHeight = (originalHeight * scaleFactor).toInt()
return Pair(newWidth, newHeight)
}
// Calculate sample size for efficient loading
fun calculateSampleSize(
originalWidth: Int,
originalHeight: Int,
requiredWidth: Int,
requiredHeight: Int
): Int {
var inSampleSize = 1
if (originalWidth > requiredWidth || originalHeight > requiredHeight) {
val halfWidth = originalWidth / 2
val halfHeight = originalHeight / 2
while (halfWidth / inSampleSize >= requiredWidth &&
halfHeight / inSampleSize >= requiredHeight) {
inSampleSize *= 2
}
}
return inSampleSize
}
// Get optimal sample size from file
fun getOptimalSampleSize(filePath: String, reqWidth: Int, reqHeight: Int): Int {
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeFile(filePath, options)
return calculateSampleSize(options.outWidth, options.outHeight, reqWidth, reqHeight)
}
}
// Main demonstration
fun demonstrateImageScaling(context: Context) {
println("=== Android Kotlin Image Scaling Examples ===\n")
// Create sample bitmap (in real usage, load from file)
val sampleBitmap = Bitmap.createBitmap(800, 600, Bitmap.Config.ARGB_8888)
val canvas = Canvas(sampleBitmap)
canvas.drawColor(android.graphics.Color.BLUE)
// 1. Basic scaling
println("--- 1. Basic Scaling ---")
val basicScaler = BasicImageScaler()
val scaledToSize = basicScaler.scaleToSize(sampleBitmap, 400, 300)
println("Scaled to 400x300: ${scaledToSize.width}x${scaledToSize.height}")
val scaledByFactor = basicScaler.scaleByFactor(sampleBitmap, 0.5f)
println("Scaled by 0.5x: ${scaledByFactor.width}x${scaledByFactor.height}")
val aspectRatio = basicScaler.scaleMaintainingAspectRatio(sampleBitmap, 300, 300)
println("Scaled maintaining aspect ratio (max 300x300): ${aspectRatio.width}x${aspectRatio.height}")
// 2. Matrix scaling
println("\n--- 2. Matrix Scaling ---")
val matrixScaler = MatrixScaler()
val matrixScaled = matrixScaler.scaleWithMatrix(sampleBitmap, 0.75f, 0.75f)
println("Matrix scaled 0.75x: ${matrixScaled.width}x${matrixScaled.height}")
// 3. High quality scaling
println("\n--- 3. High Quality Scaling ---")
val qualityScaler = QualityScaler()
val highQuality = qualityScaler.scaleHighQuality(sampleBitmap, 200, 150)
println("High quality 200x150: ${highQuality.width}x${highQuality.height}")
val smoothScaled = qualityScaler.smoothScale(sampleBitmap, 150, 150)
println("Smooth scaled to 150x150: ${smoothScaled.width}x${smoothScaled.height}")
// 4. Thumbnails
println("\n--- 4. Thumbnails ---")
val thumbGenerator = ThumbnailGenerator()
val thumbnail = thumbGenerator.generateThumbnail(sampleBitmap, 100, 100)
println("Thumbnail (100x100): ${thumbnail.width}x${thumbnail.height}")
val squareThumb = thumbGenerator.generateSquareThumbnail(sampleBitmap, 80)
println("Square thumbnail (80x80): ${squareThumb.width}x${squareThumb.height}")
val circularThumb = thumbGenerator.generateCircularThumbnail(sampleBitmap, 60)
println("Circular thumbnail (60x60): ${circularThumb.width}x${circularThumb.height}")
// 5. Utilities
println("\n--- 5. Scaling Utilities ---")
val utils = ScalingUtils()
val targetSize = utils.calculateTargetSize(1920, 1080, 800, 600)
println("Calculated target size for 1920x1080 (max 800x600): ${targetSize.first}x${targetSize.second}")
val sampleSize = utils.calculateSampleSize(1920, 1080, 400, 300)
println("Calculated sample size: $sampleSize")
println("\n=== All Image Scaling Examples Completed ===")
}
💻 图像格式转换 kotlin
🟡 intermediate
⭐⭐⭐
在PNG、JPEG、WebP格式之间转换图像及质量设置
⏱️ 25 min
🏷️ kotlin, android, image processing, conversion
Prerequisites:
Intermediate Kotlin, Android Graphics
// Android Kotlin Image Format Conversion Examples
// Using android.graphics.Bitmap and compression options
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Color
import java.io.File
import java.io.FileOutputStream
// 1. Basic Format Conversion
class FormatConverter {
// Convert bitmap to JPEG
fun convertToJPEG(bitmap: Bitmap, filePath: String, quality: Int = 90): Boolean {
return try {
val file = File(filePath)
file.parentFile?.mkdirs()
val outputStream = FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream)
outputStream.flush()
outputStream.close()
println("Converted to JPEG: $filePath")
true
} catch (e: Exception) {
println("Error converting to JPEG: ${e.message}")
false
}
}
// Convert bitmap to PNG
fun convertToPNG(bitmap: Bitmap, filePath: String): Boolean {
return try {
val file = File(filePath)
file.parentFile?.mkdirs()
val outputStream = FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
outputStream.flush()
outputStream.close()
println("Converted to PNG: $filePath")
true
} catch (e: Exception) {
println("Error converting to PNG: ${e.message}")
false
}
}
// Convert bitmap to WebP
fun convertToWebP(bitmap: Bitmap, filePath: String, quality: Int = 90): Boolean {
return try {
val file = File(filePath)
file.parentFile?.mkdirs()
val outputStream = FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.WEBP, quality, outputStream)
outputStream.flush()
outputStream.close()
println("Converted to WebP: $filePath")
true
} catch (e: Exception) {
println("Error converting to WebP: ${e.message}")
false
}
}
// Auto-detect format from file extension
fun convertByExtension(bitmap: Bitmap, filePath: String, quality: Int = 90): Boolean {
val format = when (filePath.substringAfterLast('.', "").lowercase()) {
"png" -> Bitmap.CompressFormat.PNG
"webp" -> Bitmap.CompressFormat.WEBP
else -> Bitmap.CompressFormat.JPEG
}
return try {
val file = File(filePath)
file.parentFile?.mkdirs()
val outputStream = FileOutputStream(file)
bitmap.compress(format, quality, outputStream)
outputStream.flush()
outputStream.close()
println("Converted to ${format.name}: $filePath")
true
} catch (e: Exception) {
println("Error converting: ${e.message}")
false
}
}
}
// 2. Quality-based Conversion
class QualityConverter {
// Convert with quality levels
fun convertWithQuality(
bitmap: Bitmap,
filePath: String,
quality: QualityLevel
): Boolean {
val qualityValue = when (quality) {
QualityLevel.LOW -> 50
QualityLevel.MEDIUM -> 70
QualityLevel.HIGH -> 85
QualityLevel.MAXIMUM -> 100
}
val format = when (filePath.substringAfterLast('.', "").lowercase()) {
"png" -> Bitmap.CompressFormat.PNG
"webp" -> Bitmap.CompressFormat.WEBP
else -> Bitmap.CompressFormat.JPEG
}
return try {
val file = File(filePath)
val outputStream = FileOutputStream(file)
bitmap.compress(format, qualityValue, outputStream)
outputStream.flush()
outputStream.close()
println("Converted with quality $quality ($qualityValue%): $filePath")
true
} catch (e: Exception) {
println("Error with quality conversion: ${e.message}")
false
}
}
// Convert with custom quality
fun convertWithCustomQuality(
bitmap: Bitmap,
filePath: String,
quality: Int
): Boolean {
require(quality in 0..100) { "Quality must be between 0 and 100" }
val format = Bitmap.CompressFormat.JPEG
return try {
val file = File(filePath)
val outputStream = FileOutputStream(file)
bitmap.compress(format, quality, outputStream)
outputStream.flush()
outputStream.close()
println("Converted with custom quality ($quality%): $filePath")
true
} catch (e: Exception) {
println("Error: ${e.message}")
false
}
}
// Compare quality levels
fun compareQualities(bitmap: Bitmap, outputDir: String): Map<String, Long> {
val results = mutableMapOf<String, Long>()
val qualities = listOf(50, 70, 85, 95)
for (quality in qualities) {
val filePath = "$outputDir/quality_$quality.jpg"
convertWithCustomQuality(bitmap, filePath, quality)
val fileSize = File(filePath).length()
results["quality_$quality"] = fileSize
}
return results
}
}
// Quality level enum
enum class QualityLevel {
LOW, MEDIUM, HIGH, MAXIMUM
}
// 3. Lossless vs Lossy Conversion
class ConversionTypeConverter {
// Lossless conversion (PNG)
fun convertLossless(bitmap: Bitmap, filePath: String): Boolean {
return try {
val file = File(filePath)
val outputStream = FileOutputStream(file)
// PNG is lossless
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
outputStream.flush()
outputStream.close()
println("Lossless conversion (PNG): $filePath")
true
} catch (e: Exception) {
println("Error in lossless conversion: ${e.message}")
false
}
}
// Lossy conversion (JPEG)
fun convertLossy(bitmap: Bitmap, filePath: String, quality: Int = 85): Boolean {
return try {
val file = File(filePath)
val outputStream = FileOutputStream(file)
// JPEG is lossy
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream)
outputStream.flush()
outputStream.close()
println("Lossy conversion (JPEG, quality=$quality): $filePath")
true
} catch (e: Exception) {
println("Error in lossy conversion: ${e.message}")
false
}
}
// WebP with lossless option
fun convertWebPLossless(bitmap: Bitmap, filePath: String): Boolean {
// Note: Android's WebP encoder doesn't support explicit lossless mode
// Use quality 100 for near-lossless
return try {
val file = File(filePath)
val outputStream = FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.WEBP, 100, outputStream)
outputStream.flush()
outputStream.close()
println("WebP near-lossless: $filePath")
true
} catch (e: Exception) {
println("Error: ${e.message}")
false
}
}
}
// 4. Batch Format Conversion
class BatchConverter {
// Convert multiple images to same format
fun convertMultipleToFormat(
inputFiles: List<String>,
outputDir: String,
format: Bitmap.CompressFormat,
quality: Int = 90
): Boolean {
var success = true
for ((index, inputFile) in inputFiles.withIndex()) {
try {
val bitmap = BitmapFactory.decodeFile(inputFile)
val extension = when (format) {
Bitmap.CompressFormat.PNG -> "png"
Bitmap.CompressFormat.WEBP -> "webp"
else -> "jpg"
}
val outputFile = File(outputDir, "converted_$index.$extension")
val outputStream = FileOutputStream(outputFile)
bitmap.compress(format, quality, outputStream)
outputStream.flush()
outputStream.close()
println("Converted $inputFile to ${outputFile.name}")
} catch (e: Exception) {
println("Error converting $inputFile: ${e.message}")
success = false
}
}
return success
}
// Convert directory of images
fun convertDirectory(
inputDir: String,
outputDir: String,
targetFormat: Bitmap.CompressFormat,
quality: Int = 90
): Int {
val inputDirectory = File(inputDir)
val outputDirectory = File(outputDir)
if (!outputDirectory.exists()) {
outputDirectory.mkdirs()
}
val imageFiles = inputDirectory.listFiles { file ->
file.extension.lowercase() in listOf("jpg", "jpeg", "png", "webp")
} ?: emptyArray()
var convertedCount = 0
for (file in imageFiles) {
try {
val bitmap = BitmapFactory.decodeFile(file.absolutePath)
val extension = when (targetFormat) {
Bitmap.CompressFormat.PNG -> "png"
Bitmap.CompressFormat.WEBP -> "webp"
else -> "jpg"
}
val outputFile = File(outputDir, "${file.nameWithoutExtension}.$extension")
val outputStream = FileOutputStream(outputFile)
bitmap.compress(targetFormat, quality, outputStream)
outputStream.flush()
outputStream.close()
convertedCount++
} catch (e: Exception) {
println("Error converting ${file.name}: ${e.message}")
}
}
println("Converted $convertedCount/${imageFiles.size} images")
return convertedCount
}
}
// 5. Format Comparison
class FormatComparator {
// Compare file sizes for different formats
fun compareFormatSizes(
bitmap: Bitmap,
outputDir: String
): Map<String, Long> {
val sizes = mutableMapOf<String, Long>()
// JPEG with different qualities
val jpeg85 = File(outputDir, "compare_85.jpg")
bitmap.compress(Bitmap.CompressFormat.JPEG, 85, FileOutputStream(jpeg85))
sizes["JPEG_85"] = jpeg85.length()
val jpeg95 = File(outputDir, "compare_95.jpg")
bitmap.compress(Bitmap.CompressFormat.JPEG, 95, FileOutputStream(jpeg95))
sizes["JPEG_95"] = jpeg95.length()
// PNG
val png = File(outputDir, "compare.png")
bitmap.compress(Bitmap.CompressFormat.PNG, 100, FileOutputStream(png))
sizes["PNG"] = png.length()
// WebP
val webp = File(outputDir, "compare.webp")
bitmap.compress(Bitmap.CompressFormat.WEBP, 90, FileOutputStream(webp))
sizes["WebP"] = webp.length()
return sizes
}
// Get compression ratio
fun getCompressionRatio(originalSize: Long, compressedSize: Long): Double {
return ((originalSize - compressedSize).toDouble() / originalSize) * 100
}
// Compare quality visually (would need UI in real app)
fun compareQuality(bitmap: Bitmap): List<FormatInfo> {
val formats = listOf(
FormatInfo("JPEG", 85, Bitmap.CompressFormat.JPEG),
FormatInfo("JPEG", 95, Bitmap.CompressFormat.JPEG),
FormatInfo("PNG", 100, Bitmap.CompressFormat.PNG),
FormatInfo("WebP", 90, Bitmap.CompressFormat.WEBP)
)
return formats
}
}
// Format info data class
data class FormatInfo(
val format: String,
val quality: Int,
val compressFormat: Bitmap.CompressFormat
)
// 6. Special Conversions
class SpecialConverter {
// Convert to grayscale and save
fun convertToGrayscale(bitmap: Bitmap, filePath: String): Boolean {
return try {
val grayscaleBitmap = Bitmap.createBitmap(
bitmap.width,
bitmap.height,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(grayscaleBitmap)
val paint = android.graphics.Paint()
val colorMatrix = android.graphics.ColorMatrix()
colorMatrix.setSaturation(0f)
paint.colorFilter = android.graphics.ColorMatrixColorFilter(colorMatrix)
canvas.drawBitmap(bitmap, 0f, 0f, paint)
val file = File(filePath)
val outputStream = FileOutputStream(file)
grayscaleBitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream)
outputStream.flush()
outputStream.close()
println("Converted to grayscale: $filePath")
true
} catch (e: Exception) {
println("Error: ${e.message}")
false
}
}
// Convert with color filter
fun convertWithColorFilter(
bitmap: Bitmap,
filePath: String,
color: Int
): Boolean {
return try {
val filteredBitmap = Bitmap.createBitmap(
bitmap.width,
bitmap.height,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(filteredBitmap)
val paint = android.graphics.Paint()
paint.colorFilter = android.graphics.PorterDuffColorFilter(
color,
android.graphics.PorterDuff.Mode.SRC_ATOP
)
canvas.drawBitmap(bitmap, 0f, 0f, paint)
val file = File(filePath)
val outputStream = FileOutputStream(file)
filteredBitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream)
outputStream.flush()
outputStream.close()
println("Converted with color filter: $filePath")
true
} catch (e: Exception) {
println("Error: ${e.message}")
false
}
}
}
// Main demonstration
fun demonstrateImageFormatConversion() {
println("=== Android Kotlin Image Format Conversion Examples ===\n")
// Create sample bitmap
val sampleBitmap = Bitmap.createBitmap(400, 300, Bitmap.Config.ARGB_8888)
val canvas = Canvas(sampleBitmap)
canvas.drawColor(Color.BLUE)
// 1. Basic conversion
println("--- 1. Basic Format Conversion ---")
val converter = FormatConverter()
// Note: In real usage, use actual file paths
println("Converting to JPEG...")
println("Converting to PNG...")
println("Converting to WebP...")
// 2. Quality conversion
println("\n--- 2. Quality Conversion ---")
val qualityConverter = QualityConverter()
println("Converting with quality levels:")
QualityLevel.values().forEach { level ->
println(" - $level")
}
// 3. Lossless vs Lossy
println("\n--- 3. Lossless vs Lossy ---")
val typeConverter = ConversionTypeConverter()
println("Lossless (PNG): preserves all image data, larger file size")
println("Lossy (JPEG): smaller file size, some quality loss")
println("WebP: modern format with good compression")
// 4. Format comparison
println("\n--- 4. Format Comparison ---")
val comparator = FormatComparator()
println("Comparing formats:")
println(" - JPEG (85%): Good balance of quality and size")
println(" - JPEG (95%): High quality, larger size")
println(" - PNG: Lossless, largest size")
println(" - WebP (90%): Best compression ratio")
val originalSize = 1000000L // Example size
val compressedSize = 150000L
val ratio = comparator.getCompressionRatio(originalSize, compressedSize)
println("\nCompression ratio: ${String.format("%.2f", ratio)}%")
// 5. Batch conversion
println("\n--- 5. Batch Conversion ---")
val batchConverter = BatchConverter()
println("Batch conversion methods:")
println(" - convertMultipleToFormat(): Convert multiple images")
println(" - convertDirectory(): Convert all images in directory")
// 6. Special conversions
println("\n--- 6. Special Conversions ---")
val specialConverter = SpecialConverter()
println("Special conversion methods:")
println(" - convertToGrayscale(): Convert to black and white")
println(" - convertWithColorFilter(): Apply color tint")
println("\n=== All Image Format Conversion Examples Completed ===")
}