Binary Data & Image Format

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

In this segment, you’ll learn how to convert Android Bitmap images to a format that Swift can process. You’ll create a custom file format for passing image data across the JNI boundary and implement the conversion logic in both directions.

Understanding image data representation is crucial for cross-language image processing. While Android uses Bitmap objects and Swift uses Data, both need to work with the same underlying pixel information.

Understanding RGBA Image Format

Digital images are composed of pixels, and each pixel contains color information. The RGBA format represents each pixel with four values:

Designing a Custom File Format

To pass image data between Kotlin and Swift, you’ll create a simple custom file format. While you could use standard formats like PNG or JPEG, a custom format is simpler for this educational purpose and demonstrates the data marshaling process clearly.

Hixz: zopoexfa kovvrh Viewik: 1 rtley Fbsuv 3-1: Cagxt (Ivx94, zob-efwuog) HFYE bepaq qoya (fudcs l xuamyr v 7 dpgow) Drvag 1-1: Wiavhz (Usk13, qer-eyteur) Wujyuh Sofu Xerfuz
Pajsuh hega quxmej rpfijsopu

Creating the Image Processing Repository

Now create a repository to handle image format conversion and Swift filter calls.

package com.kodeco.android.swiftsdkforandroid.taskmanager.repository

import android.content.Context
import android.graphics.Bitmap
import com.kodeco.android.taskmanagerkit.ImageProcessor
import java.io.File
import java.io.FileOutputStream

// 1
object ImageProcessingRepository {

  // 2
  enum class FilterType(val swiftName: String) {
    GRAYSCALE("grayscale"),
    BLUR("blur"),
    BRIGHTER("brighter"),
    DARKER("darker")
  }

  // 3
  fun applyFilter(
    bitmap: Bitmap,
    filterType: FilterType,
    context: Context,
    amount: Float = 0f
  ): Bitmap? {
    try {
      // 4
      val scaledBitmap = downscaleForProcessing(bitmap, maxDimension = 1024)

      val width = scaledBitmap.width
      val height = scaledBitmap.height

      // 5
      val inputFile = File(context.cacheDir, "filter_input_${System.currentTimeMillis()}.rgba")
      FileOutputStream(inputFile).use { out ->
        // 6
        out.write(width shr 24)
        out.write(width shr 16)
        out.write(width shr 8)
        out.write(width)
        out.write(height shr 24)
        out.write(height shr 16)
        out.write(height shr 8)
        out.write(height)

        // 7
        val pixels = IntArray(width * height)
        scaledBitmap.getPixels(pixels, 0, width, 0, 0, width, height)
        for (pixel in pixels) {
          out.write((pixel shr 16) and 0xFF) // R
          out.write((pixel shr 8) and 0xFF)  // G
          out.write(pixel and 0xFF)          // B
          out.write((pixel shr 24) and 0xFF) // A
        }
      }

      // 8
      val outputFile = File(context.cacheDir, "filter_output_${System.currentTimeMillis()}.rgba")

      // 9
      val success = ImageProcessor.processImageFile(
        inputFile.absolutePath,
        outputFile.absolutePath,
        filterType.swiftName,
        amount
      )

      // 10
      val result = if (success && outputFile.exists()) {
        val bytes = outputFile.readBytes()
        if (bytes.size >= 8) {
          // 11
          val w = ((bytes[0].toInt() and 0xFF) shl 24) or
                  ((bytes[1].toInt() and 0xFF) shl 16) or
                  ((bytes[2].toInt() and 0xFF) shl 8) or
                  (bytes[3].toInt() and 0xFF)
          val h = ((bytes[4].toInt() and 0xFF) shl 24) or
                  ((bytes[5].toInt() and 0xFF) shl 16) or
                  ((bytes[6].toInt() and 0xFF) shl 8) or
                  (bytes[7].toInt() and 0xFF)

          // 12
          val pixelData = IntArray(w * h)
          var byteIndex = 8
          for (i in pixelData.indices) {
            val r = bytes[byteIndex++].toInt() and 0xFF
            val g = bytes[byteIndex++].toInt() and 0xFF
            val b = bytes[byteIndex++].toInt() and 0xFF
            val a = bytes[byteIndex++].toInt() and 0xFF
            pixelData[i] = (a shl 24) or (r shl 16) or (g shl 8) or b
          }

          val resultBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
          resultBitmap.setPixels(pixelData, 0, w, 0, 0, w, h)
          resultBitmap
        } else {
          null
        }
      } else {
        null
      }

      // 13
      inputFile.delete()
      outputFile.delete()

      return result
    } catch (e: Exception) {
      e.printStackTrace()
      return null
    }
  }

  // 14
  private fun downscaleForProcessing(bitmap: Bitmap, maxDimension: Int): Bitmap {
    val width = bitmap.width
    val height = bitmap.height

    // 15
    if (width <= maxDimension && height <= maxDimension) {
      return bitmap
    }

    // 16
    val scale = if (width > height) {
      maxDimension.toFloat() / width
    } else {
      maxDimension.toFloat() / height
    }

    val newWidth = (width * scale).toInt()
    val newHeight = (height * scale).toInt()

    return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true)
  }
}

Understanding Bit Shifting for Header Encoding

The header encoding uses bit shift operations to convert a 32-bit integer into 4 bytes.

val width = 1024  // 0x00000400 in hexadecimal
out.write(width shr 24)  // Writes 0x00 (byte 0)
out.write(width shr 16)  // Writes 0x00 (byte 1)
out.write(width shr 8)   // Writes 0x04 (byte 2)
out.write(width)         // Writes 0x00 (byte 3)
let width = Int32((UInt32(bytes[0]) << 24) |
                  (UInt32(bytes[1]) << 16) |
                  (UInt32(bytes[2]) << 8) |
                  UInt32(bytes[3]))

Understanding Pixel Format Conversion

Android stores pixels as ARGB in a packed Int, but you need to convert them to separate RGBA bytes for the file format.

Bit position: 31-24  23-16  15-8   7-0
Value:        Alpha  Red    Green  Blue
Byte 0: Red
Byte 1: Green
Byte 2: Blue
Byte 3: Alpha
val pixel = pixels[i]  // Example: 0xFF800000 (red, fully opaque)
val r = (pixel shr 16) and 0xFF  // Extracts 0x80 (128)
val g = (pixel shr 8) and 0xFF   // Extracts 0x00 (0)
val b = pixel and 0xFF           // Extracts 0x00 (0)
val a = (pixel shr 24) and 0xFF  // Extracts 0xFF (255)
val pixel = (a shl 24) or (r shl 16) or (g shl 8) or b

Understanding the Complete Data Flow

Here’s how image data flows between Kotlin and Swift:

1. Tudgor Qutbuv 5. Gofpabv zu ZLVO YymuIrpif 1. Ppupi Paxe Diured + Dehazx 0. Hretj Xuuy Hube 6. Ttewipb Aqcxj Jebhok 8. Bsise Bape Giggikut Maca 1. Mixnan Muav Tega 9. Dixweft fo Risyex 1. Kewfpab ok OE
Qujlcore gipu wnob rajnait Sugnik ejq Xpesr

Key Takeaways

In this segment, you’ve:

See forum comments
Download course materials from Github
Previous: Setting Up Camera Integration Next: Implementing Image Filters in Swift