static void BlurFilter_iterativeBoxBlur()

in native-filters/src/main/jni/filters/blur_filter.c [159:268]


static void BlurFilter_iterativeBoxBlur(
    JNIEnv* env,
    jclass clazz,
    jobject bitmap,
    jint iterations,
    jint radius) {
  UNUSED(clazz);

  if (iterations <= 0 || iterations > BLUR_MAX_ITERATIONS) {
    safe_throw_exception(env, "BlurFilter_iterativeBoxBlur: Iterations argument out of bounds");
    return;
  }

  if (radius <= 0 || radius > BLUR_MAX_RADIUS) {
    safe_throw_exception(env, "BlurFilter_iterativeBoxBlur: Blur radius argument out of bounds");
    return;
  }

  AndroidBitmapInfo bitmapInfo;

  int rc = AndroidBitmap_getInfo(env, bitmap, &bitmapInfo);
  if (rc != ANDROID_BITMAP_RESULT_SUCCESS) {
    safe_throw_exception(env, "BlurFilter_iterativeBoxBlur: Failed to get Bitmap info");
    return;
  }

  if (bitmapInfo.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
    safe_throw_exception(env, "BlurFilter_iterativeBoxBlur: Unexpected bitmap format");
    return;
  }

  pixel_t* pixelPtr;

  const int w = bitmapInfo.width;
  const int h = bitmapInfo.height;

  if (w > BITMAP_MAX_DIMENSION || h > BITMAP_MAX_DIMENSION) {
    safe_throw_exception(env, "BlurFilter_iterativeBoxBlur: Bitmap dimensions too large");
    return;
  }

  // locking pixels such that they will not get moved around during processing
  rc = AndroidBitmap_lockPixels(env, bitmap, (void*) &pixelPtr);
  if (rc != ANDROID_BITMAP_RESULT_SUCCESS) {
    safe_throw_exception(env, "BlurFilter_iterativeBoxBlur: Failed to lock Bitmap pixels");
    return;
  }

  // the information written to an output pixels `x` are from `[x-radius, x+radius]` (inclusive)
  const int diameter = radius + 1 + radius;

  // pre-compute division table: speed-up by factor 5(!)
  uint8_t* div = (uint8_t*) malloc(256 * diameter * sizeof(uint8_t));
  if (!div) {
    AndroidBitmap_unlockPixels(env, bitmap);
    safe_throw_exception(env, "BlurFilter_iterativeBoxBlur: Failed to allocate memory: div");
    return;
  }

  // the following lines will fill-up at least the first `255 * diameter` entries with the mapping
  // `div[x] = (x + r) / d` (i.e. division of x by d rounded to the nearest number).
  uint8_t* ptr = div;
  for (int r = 0; r <= radius; r++) {
    *(ptr++) = 0;
  }
  for (int b = 1; b <= 255; b++) {
    for (int d = 0; d < diameter; d++) {
      *(ptr++) = b;
    }
  }

  // temporary array for the output of the currently blurred row OR column
  pixel_t* tempRowOrColumn = (pixel_t*) malloc(max(w, h) * sizeof(pixel_t));
  if (!tempRowOrColumn) {
    free(div);
    AndroidBitmap_unlockPixels(env, bitmap);
    safe_throw_exception(env, "BlurFilter_iterativeBoxBlur: Failed to allocate memory: tempRowOrColumn");
    return;
  }

  for (int i = 0; i < iterations; i++) {
    // blur rows one-by-one
    for (int row = 0; row < h; row++) {
      internalHorizontalBlur(pixelPtr, tempRowOrColumn, w, row, diameter, div);

      // copy output row pixels back to bitmap
      memcpy(pixelPtr + w * row, tempRowOrColumn, w * sizeof(pixel_t));
    }

    // blur columns one-by-one
    for (int col = 0; col < w; col++) {
      internalVerticalBlur(pixelPtr, tempRowOrColumn, w, h, col, diameter, div);

      // copy output column pixels back to bitmap
      pixel_t* ptr = pixelPtr + col;
      for (int row = 0; row < h; row++) {
        *ptr = tempRowOrColumn[row];
        ptr += w;
      }
    }
  }

  free(tempRowOrColumn);
  free(div);

  rc = AndroidBitmap_unlockPixels(env, bitmap);
  if (rc != ANDROID_BITMAP_RESULT_SUCCESS) {
    safe_throw_exception(env, "BlurFilter_iterativeBoxBlur: Failed to unlock Bitmap pixels");
  }
}