Ejemplos de Procesamiento de Imágenes Android Kotlin

Ejemplos de procesamiento de imágenes Android Kotlin incluyendo lectura/escritura, escalado y conversión de formato

💻 Leer y Guardar Imágenes kotlin

🟡 intermediate ⭐⭐⭐

Leer imágenes de varias fuentes y guardarlas en almacenamiento

⏱️ 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 ===")
}

💻 Escalado de Imágenes kotlin

🟡 intermediate ⭐⭐⭐⭐

Redimensionar y escalar imágenes con varios algoritmos y configuraciones de calidad

⏱️ 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 ===")
}

💻 Conversión de Formato de Imagen kotlin

🟡 intermediate ⭐⭐⭐

Convertir imágenes entre formatos PNG, JPEG, WebP con configuraciones de calidad

⏱️ 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 ===")
}