Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ learning_objectives:
- Integrating Halide into an Android (Kotlin) Project

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please make the punctuation consistent between list items.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?


prerequisites:

- Basic C++ knowledge
- Basic programming knowledge
- Android Studio with Android Emulator

author: Dawid Borycki

Expand All @@ -36,6 +38,10 @@ further_reading:
title: Halide GitHub
link: https://github.com/halide/Halide
type: repository
- resource:
title: Halide Tutorials
link: https://halide-lang.org/tutorials/
type: website


### FIXED, DO NOT MODIFY
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,39 @@ layout: "learningpathall"
## Objective
In this lesson, we’ll learn how to integrate a high-performance Halide image-processing pipeline into an Android application using Kotlin.

## Overview of Mobile Integration with Halide
## Overview of mobile integration with Halide
Android is the world’s most widely-used mobile operating system, powering billions of devices across diverse markets. This vast user base makes Android an ideal target platform for developers aiming to reach a broad audience, particularly in applications requiring sophisticated image and signal processing, such as augmented reality, photography, video editing, and real-time analytics.

Kotlin, now the preferred programming language for Android development, combines concise syntax with robust language features, enabling developers to write maintainable, expressive, and safe code. It offers seamless interoperability with existing Java codebases and straightforward integration with native code via JNI, simplifying the development of performant mobile applications.

## Benefits of Using Halide on Mobile
## Benefits of using Halide on mobile
Integrating Halide into Android applications brings several key advantages:
1. Performance. Halide enables significant acceleration of complex image processing algorithms, often surpassing the speed of traditional Java or Kotlin implementations by leveraging optimized code generation. By generating highly optimized native code tailored for ARM CPUs or GPUs, Halide can dramatically increase frame rates and responsiveness, essential for real-time or interactive applications.
2. Efficiency. On mobile devices, resource efficiency translates directly to improved battery life and reduced thermal output. Halide’s scheduling strategies (such as operation fusion, tiling, parallelization, and vectorization) minimize unnecessary memory transfers, CPU usage, and GPU overhead. This optimization substantially reduces overall power consumption, extending battery life and enhancing the user experience by preventing overheating.
3. Portability. Halide abstracts hardware-specific details, allowing developers to write a single high-level pipeline that easily targets different processor architectures and hardware configurations. Pipelines can seamlessly run on various ARM-based CPUs and GPUs commonly found in Android smartphones and tablets, enabling developers to support a wide range of devices with minimal platform-specific modifications.

In short, Halide delivers high-performance image processing without sacrificing portability or efficiency, a balance particularly valuable on resource-constrained mobile devices.

### Android Development Ecosystem and Challenges
### Android development ecosystem and challenges
While Android presents abundant opportunities for developers, the mobile development ecosystem brings its own set of challenges, especially for performance-intensive applications:
1. Limited Hardware Resources. Unlike desktop or server environments, mobile devices have significant constraints on processing power, memory capacity, and battery life. Developers must optimize software meticulously to deliver smooth performance while carefully managing hardware resource consumption. Leveraging tools like Halide allows developers to overcome these constraints by optimizing computational workloads, making resource-intensive tasks feasible on constrained hardware.
2. Cross-Compilation Complexities. Developing native code for Android requires handling multiple hardware architectures (such as ARMv7, ARM64, and sometimes x86/x86_64). Cross-compilation introduces complexities due to different instruction sets, CPU features, and performance characteristics. Managing this complexity involves careful use of the Android NDK, understanding toolchains, and correctly configuring build systems (e.g., Gradle, CMake). Halide helps mitigate these issues by abstracting away many platform-specific optimizations, automatically generating code optimized for target architectures.
2. Cross-Compilation Complexities. Developing native code for Android requires handling multiple hardware architectures (such as armv8-a, ARM64, and sometimes x86/x86_64). Cross-compilation introduces complexities due to different instruction sets, CPU features, and performance characteristics. Managing this complexity involves careful use of the Android NDK, understanding toolchains, and correctly configuring build systems (e.g., Gradle, CMake). Halide helps mitigate these issues by abstracting away many platform-specific optimizations, automatically generating code optimized for target architectures.
3. Image-Format Conversions (Bitmap ↔ Halide Buffer). Android typically handles images through the Bitmap class or similar platform-specific constructs, whereas Halide expects image data to be in raw, contiguous buffer formats. Developers must bridge the gap between Android-specific image representations (Bitmaps, YUV images from camera APIs, etc.) and Halide’s native buffer format. Proper management of these conversions—including considerations for pixel formats, stride alignment, and memory copying overhead—can significantly impact performance and correctness, necessitating careful design and efficient implementation of buffer-handling routines.

## Project Requirements
## Project requirements
Before integrating Halide into your Android application, ensure you have the necessary tools and libraries.

### Tools and Prerequisites
### Tools and prerequisites
1. Android Studio. [Download link](https://developer.android.com/studio).
2. Android NDK (Native Development Kit). Can be easily installed from Android Studio (Tools → SDK Manager → SDK Tools → Android NDK).

## Setting Up the Android Project
### Creating the Project:
## Setting up the Android project
### Creating the project
1. Open Android Studio.
2. Select New Project > Native C++.
![img4](Figures/04.png)

### Configure the Project:
### Configure the project
1. Set the project Name to Arm.Halide.AndroidDemo.
2. Choose Kotlin as the language.
3. Set Minimum SDK to API 24.
Expand All @@ -52,7 +52,7 @@ Before integrating Halide into your Android application, ensure you have the nec
![img6](Figures/06.png)
6. Click Finish.

## Configuring the Android Project
## Configuring the Android project
Next, configure your Android project to use the files generated in the previous step. First, copy blur_threshold_android.a and blur_threshold_android.h into ArmHalideAndroidDemo/app/src/main/cpp. Ensure your cpp directory contains the following files:
* native-lib.cpp
* blur_threshold_android.a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Create a new file named blur-android.cpp with the following contents:
```cpp
#include "Halide.h"
#include <iostream>
#include <string> // for std::string
#include <cstdint> // for fixed-width integer types (e.g., uint8_t)
using namespace Halide;

int main(int argc, char** argv) {
Expand Down Expand Up @@ -85,6 +87,10 @@ int main(int argc, char** argv) {
}
```

In the original implementation constants 128, 255, and 0 were implicitly treated as integers. Here, the threshold value (128) and output values (255, 0) are explicitly cast to uint8_t. This approach removes ambiguity and clearly specifies the types used, ensuring compatibility and clarity. Both approaches result in identical functionality, but explicitly casting helps emphasize the type correctness and may avoid subtle issues during cross-compilation or in certain environments.

Both approaches result in identical functionality, but explicitly casting helps emphasize the type correctness and may avoid subtle issues during cross-compilation or in certain environments.

The program takes at least one command-line argument, the output base name used to generate the files (e.g., “blur_threshold_android”). Here, the target architecture is explicitly set within the code to Android ARM64:

```cpp
Expand All @@ -93,11 +99,24 @@ Halide::Target target;
target.os = Halide::Target::OS::Android;
target.arch = Halide::Target::Arch::ARM;
target.bits = 64;

// Enable Halide runtime inclusion in the generated library (needed if not linking Halide runtime separately).
target.set_feature(Target::NoRuntime, false);

// Optionally, enable hardware-specific optimizations to improve performance on ARM devices:
// - NEON: Advanced SIMD (Single Instruction Multiple Data) instructions for parallel computation.
// - DotProd: Optimizes matrix multiplication and convolution-like operations on ARM.
// - SVE or SVE2: Scalable Vector Extension for vectorization on newer ARM architectures (if applicable).
```

Notes:
* NoRuntime feature: When set to true, Halide excludes its runtime from the generated code, requiring you to link the runtime manually during the linking step. Setting it to false includes the Halide runtime within the generated library, simplifying deployment.
* NEON, DotProd, and SVE/SVE2 features leverage ARM-specific instruction sets for enhanced performance, especially beneficial on Android mobile devices.

We declare spatial variables (x, y) and an ImageParam named “input” representing the input image data. We use boundary clamping (clamp) to safely handle edge pixels. Then, we apply a 3x3 blur with a reduction domain (RDom). The accumulated sum is divided by 9 (the number of pixels in the neighborhood), producing an average blurred image. Lastly, thresholding is applied, producing a binary output: pixels above a certain brightness threshold (128) become white (255), while others become black (0).

This section intentionally reinforces previous concepts, focusing now primarily on explicitly clarifying integration details, such as type correctness and the handling of runtime features within Halide.

Simple scheduling directives (compute_root) instruct Halide to compute intermediate functions at the pipeline’s root, simplifying debugging and potentially enhancing runtime efficiency.

We invoke Halide’s AOT compilation function compile_to_static_library, which generates a static library (.a) containing the optimized pipeline and a corresponding header file (.h).
Expand All @@ -117,6 +136,8 @@ This will produce:

These generated files are then ready to integrate directly into an Android project via JNI, allowing efficient execution of the optimized pipeline on Android devices. The integration process is covered in the next section.

Note: JNI (Java Native Interface) is a framework that allows Java (or Kotlin) code running in a Java Virtual Machine (JVM), such as on Android, to interact with native applications and libraries written in languages like C or C++. JNI bridges the managed Java/Kotlin environment and the native, platform-specific implementations.

## Compilation instructions
To compile the pipeline-generation program on your host system, use the following commands (replace /path/to/halide with your Halide installation directory):
```console
Expand Down
Loading
Loading