Crop border in Rust (#1631)
* Fix deref_mut_direct_bytebuffer lifetime Signed-off-by: Dude so hot <djohn@fbi.gov> * Framework Signed-off-by: Dude so hot <djohn@fbi.gov> * Add Infra Signed-off-by: Dude so hot <djohn@fbi.gov> * Implement Signed-off-by: Dude so hot <djohn@fbi.gov> * Refine Signed-off-by: Dude so hot <djohn@fbi.gov> * Impl Signed-off-by: Dude so hot <djohn@fbi.gov> * Fixes Signed-off-by: Dude so hot <djohn@fbi.gov> * Implement Signed-off-by: Dude so hot <djohn@fbi.gov> * Update profile code * Remove corrent but slow code that slow for 1000x Signed-off-by: Dude so hot <djohn@fbi.gov> * Revert "Remove corrent but slow code that slow for 1000x" This reverts commit 644d093ddc20f4a129ee304157767707337d149d. * Fix speed Signed-off-by: Dude so hot <djohn@fbi.gov> * refine Signed-off-by: Dude so hot <djohn@fbi.gov> * refine Signed-off-by: Dude so hot <djohn@fbi.gov> * Refine Signed-off-by: Dude so hot <djohn@fbi.gov> * cleanup type conversion Signed-off-by: Dude so hot <djohn@fbi.gov> * cleanup Signed-off-by: Dude so hot <djohn@fbi.gov> * refine Signed-off-by: Dude so hot <djohn@fbi.gov> * Remove C cropborder impl * impl nth to accel Signed-off-by: Dude so hot <djohn@fbi.gov> * F**K Signed-off-by: Dude so hot <djohn@fbi.gov> --------- Signed-off-by: Dude so hot <djohn@fbi.gov> Co-authored-by: Dude so hot <djohn@fbi.gov>
This commit is contained in:
1 parent
9bc6cccf26
commit
eaf8dd21ff
5 files changed
+99
-244
No files matched your search
@@ -107,5 +107,5 @@ corrosion_import_crate(MANIFEST_PATH ../rust/Cargo.toml)
|
||||
corrosion_add_target_local_rustflags(ehviewer_rust "-Clink-args=-Wl,-z,max-page-size=16384")
|
||||
|
||||
# Build and link our app's native lib
|
||||
add_library(${PROJECT_NAME} SHARED archive.c border.c gifutils.c hash.c natsort/strnatcmp.c)
|
||||
add_library(${PROJECT_NAME} SHARED archive.c gifutils.c hash.c natsort/strnatcmp.c)
|
||||
target_link_libraries(${PROJECT_NAME} ehviewer_rust archive_static log jnigraphics -Wl,--exclude-libs,ALL)
|
||||
@@ -1,241 +0,0 @@
|
||||
#include <jni.h>
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
#include <android/bitmap.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/** A line will be considered as having content if 0.25% of it is filled. */
|
||||
const float filledRatioLimit = 0.0025;
|
||||
|
||||
/** When the threshold is closer to 1, less content will be cropped. **/
|
||||
#define THRESHOLD 0.75
|
||||
|
||||
const uint8_t thresholdForBlack = (uint8_t) (255.0 * THRESHOLD);
|
||||
|
||||
const uint8_t thresholdForWhite = (uint8_t) (255.0 - 255.0 * THRESHOLD);
|
||||
|
||||
static uint8_t inline grey(uint32_t pixel) {
|
||||
const uint8_t r = ((uint8_t *) &pixel)[0];
|
||||
const uint8_t g = ((uint8_t *) &pixel)[1];
|
||||
const uint8_t b = ((uint8_t *) &pixel)[2];
|
||||
return (r * 77 + g * 151 + b * 28) >> 8;
|
||||
}
|
||||
|
||||
static bool inline isBlackPixel(const uint32_t *pixels, uint32_t width, uint32_t x, uint32_t y) {
|
||||
const uint32_t pixel = *((uint32_t *) pixels + (y * width + x));
|
||||
return grey(pixel) < thresholdForBlack;
|
||||
}
|
||||
|
||||
static bool inline isWhitePixel(const uint32_t *pixels, uint32_t width, uint32_t x, uint32_t y) {
|
||||
const uint32_t pixel = *((uint32_t *) pixels + (y * width + x));
|
||||
return grey(pixel) > thresholdForWhite;
|
||||
}
|
||||
|
||||
/** Return the first x position where there is a substantial amount of fill,
|
||||
* starting the search from the left. */
|
||||
uint32_t findBorderLeft(uint32_t *pixels, uint32_t width, uint32_t height,
|
||||
uint32_t top, uint32_t bottom) {
|
||||
int x, y;
|
||||
const uint32_t filledLimit = round(height * filledRatioLimit / 2);
|
||||
|
||||
// Scan first line to detect dominant color
|
||||
uint32_t whitePixels = 0;
|
||||
uint32_t blackPixels = 0;
|
||||
|
||||
for (y = top; y < bottom; y += 2) {
|
||||
if (isBlackPixel(pixels, width, 0, y)) {
|
||||
blackPixels++;
|
||||
} else if (isWhitePixel(pixels, width, 0, y)) {
|
||||
whitePixels++;
|
||||
}
|
||||
}
|
||||
|
||||
bool (*detectFunc)(const uint32_t *, uint32_t, uint32_t, uint32_t) = isBlackPixel;
|
||||
if (whitePixels > filledLimit && blackPixels > filledLimit) {
|
||||
// Mixed fill found... don't crop anything
|
||||
return 0;
|
||||
} else if (blackPixels > filledLimit) {
|
||||
detectFunc = isWhitePixel;
|
||||
}
|
||||
|
||||
// Scan vertical lines in search of filled lines
|
||||
for (x = 1; x < width; x++) {
|
||||
uint32_t filledCount = 0;
|
||||
|
||||
for (y = top; y < bottom; y += 2) {
|
||||
if (detectFunc(pixels, width, x, y)) {
|
||||
filledCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (filledCount > filledLimit) {
|
||||
// This line contains enough fill
|
||||
return x;
|
||||
}
|
||||
}
|
||||
|
||||
// No fill found... don't crop anything
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Return the first x position where there is a substantial amount of fill,
|
||||
* starting the search from the right. */
|
||||
uint32_t findBorderRight(uint32_t *pixels, uint32_t width, uint32_t height,
|
||||
uint32_t top, uint32_t bottom) {
|
||||
int x, y;
|
||||
const uint32_t filledLimit = round(height * filledRatioLimit / 2);
|
||||
|
||||
// Scan first line to detect dominant color
|
||||
uint32_t whitePixels = 0;
|
||||
uint32_t blackPixels = 0;
|
||||
|
||||
uint32_t lastX = width - 1;
|
||||
for (y = top; y < bottom; y += 2) {
|
||||
if (isBlackPixel(pixels, width, lastX, y)) {
|
||||
blackPixels++;
|
||||
} else if (isWhitePixel(pixels, width, lastX, y)) {
|
||||
whitePixels++;
|
||||
}
|
||||
}
|
||||
|
||||
bool (*detectFunc)(const uint32_t *, uint32_t, uint32_t, uint32_t) = isBlackPixel;
|
||||
if (whitePixels > filledLimit && blackPixels > filledLimit) {
|
||||
// Mixed fill found... don't crop anything
|
||||
return width;
|
||||
} else if (blackPixels > filledLimit) {
|
||||
detectFunc = isWhitePixel;
|
||||
}
|
||||
|
||||
// Scan vertical lines in search of filled lines
|
||||
for (x = width - 2; x > 0; x--) {
|
||||
uint32_t filledCount = 0;
|
||||
|
||||
for (y = top; y < bottom; y += 2) {
|
||||
if (detectFunc(pixels, width, x, y)) {
|
||||
filledCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (filledCount > filledLimit) {
|
||||
// This line contains enough fill
|
||||
return x + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// No fill found... don't crop anything
|
||||
return width;
|
||||
}
|
||||
|
||||
/** Return the first y position where there is a substantial amount of fill,
|
||||
* starting the search from the top. */
|
||||
uint32_t findBorderTop(uint32_t *pixels, uint32_t width, uint32_t height) {
|
||||
int x, y;
|
||||
const uint32_t filledLimit = round(width * filledRatioLimit / 2);
|
||||
|
||||
// Scan first line to detect dominant color
|
||||
uint32_t whitePixels = 0;
|
||||
uint32_t blackPixels = 0;
|
||||
|
||||
for (x = 0; x < width; x += 2) {
|
||||
if (isBlackPixel(pixels, width, x, 0)) {
|
||||
blackPixels++;
|
||||
} else if (isWhitePixel(pixels, width, x, 0)) {
|
||||
whitePixels++;
|
||||
}
|
||||
}
|
||||
|
||||
bool (*detectFunc)(const uint32_t *, uint32_t, uint32_t, uint32_t) = isBlackPixel;
|
||||
if (whitePixels > filledLimit && blackPixels > filledLimit) {
|
||||
// Mixed fill found... don't crop anything
|
||||
return 0;
|
||||
} else if (blackPixels > filledLimit) {
|
||||
detectFunc = isWhitePixel;
|
||||
}
|
||||
|
||||
// Scan horizontal lines in search of filled lines
|
||||
for (y = 1; y < height; y++) {
|
||||
uint32_t filledCount = 0;
|
||||
|
||||
for (x = 0; x < width; x += 2) {
|
||||
if (detectFunc(pixels, width, x, y)) {
|
||||
filledCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (filledCount > filledLimit) {
|
||||
// This line contains enough fill
|
||||
return y;
|
||||
}
|
||||
}
|
||||
|
||||
// No fill found... don't crop anything
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Return the first y position where there is a substantial amount of fill,
|
||||
* starting the search from the bottom. */
|
||||
uint32_t findBorderBottom(uint32_t *pixels, uint32_t width, uint32_t height) {
|
||||
int x, y;
|
||||
const uint32_t filledLimit = round(width * filledRatioLimit / 2);
|
||||
|
||||
// Scan first line to detect dominant color
|
||||
uint32_t whitePixels = 0;
|
||||
uint32_t blackPixels = 0;
|
||||
uint32_t lastY = height - 1;
|
||||
|
||||
for (x = 0; x < width; x += 2) {
|
||||
if (isBlackPixel(pixels, width, x, lastY)) {
|
||||
blackPixels++;
|
||||
} else if (isWhitePixel(pixels, width, x, lastY)) {
|
||||
whitePixels++;
|
||||
}
|
||||
}
|
||||
|
||||
bool (*detectFunc)(const uint32_t *, uint32_t, uint32_t, uint32_t) = isBlackPixel;
|
||||
if (whitePixels > filledLimit && blackPixels > filledLimit) {
|
||||
// Mixed fill found... don't crop anything
|
||||
return height;
|
||||
} else if (blackPixels > filledLimit) {
|
||||
detectFunc = isWhitePixel;
|
||||
}
|
||||
|
||||
// Scan horizontal lines in search of filled lines
|
||||
for (y = height - 2; y > 0; y--) {
|
||||
uint32_t filledCount = 0;
|
||||
|
||||
for (x = 0; x < width; x += 2) {
|
||||
if (detectFunc(pixels, width, x, y)) {
|
||||
filledCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (filledCount > filledLimit) {
|
||||
// This line contains enough fill
|
||||
return y + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// No fill found... don't crop anything
|
||||
return height;
|
||||
}
|
||||
|
||||
JNIEXPORT jintArray JNICALL
|
||||
Java_com_hippo_ehviewer_image_ImageKt_detectBorder(JNIEnv *env, jclass clazz, jobject bitmap) {
|
||||
void *pixels = NULL;
|
||||
AndroidBitmapInfo info;
|
||||
memset(&info, 0, sizeof(info));
|
||||
AndroidBitmap_getInfo(env, bitmap, &info);
|
||||
AndroidBitmap_lockPixels(env, bitmap, &pixels);
|
||||
uint32_t width = info.width;
|
||||
uint32_t height = info.height;
|
||||
uint32_t top = findBorderTop(pixels, width, height);
|
||||
uint32_t bottom = findBorderBottom(pixels, width, height);
|
||||
uint32_t left = findBorderLeft(pixels, width, height, top, bottom);
|
||||
uint32_t right = findBorderRight(pixels, width, height, top, bottom);
|
||||
int array[4] = {left, top, right, bottom};
|
||||
AndroidBitmap_unlockPixels(env, bitmap);
|
||||
jintArray ret = (*env)->NewIntArray(env, 4);
|
||||
(*env)->SetIntArrayRegion(env, ret, 0, 4, array);
|
||||
return ret;
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
mod border;
|
||||
mod qr_code;
|
||||
@@ -0,0 +1,95 @@
|
||||
use crate::{jni_throwing, with_bitmap_content};
|
||||
use anyhow::anyhow;
|
||||
use image::{ImageBuffer, Luma, Pixel, Rgba};
|
||||
use jni::objects::JClass;
|
||||
use jni::sys::{jintArray, jobject};
|
||||
use jni::JNIEnv;
|
||||
use jni_fn::jni_fn;
|
||||
|
||||
/** A line will be considered as having content if 0.25% of it is filled. */
|
||||
static FILLED_RATIO_LIMIT: f32 = 0.0025;
|
||||
|
||||
/** When the threshold is closer to 1, less content will be cropped. **/
|
||||
static THRESHOLD: f32 = 0.75;
|
||||
|
||||
static THRESHOLD_FOR_BLACK: u8 = (255.0 * THRESHOLD) as u8;
|
||||
static THRESHOLD_FOR_WHITE: u8 = (255.0 - 255.0 * THRESHOLD) as u8;
|
||||
|
||||
fn is_white(pixel: &Luma<u8>) -> bool {
|
||||
pixel.0[0] > THRESHOLD_FOR_WHITE
|
||||
}
|
||||
|
||||
fn is_black(pixel: &Luma<u8>) -> bool {
|
||||
pixel.0[0] < THRESHOLD_FOR_BLACK
|
||||
}
|
||||
|
||||
fn line_not_filled_by<'pixel>(
|
||||
line: impl Iterator<Item = &'pixel Rgba<u8>>,
|
||||
white: bool,
|
||||
limit: i32,
|
||||
) -> bool {
|
||||
let f = if white { is_black } else { is_white };
|
||||
line.step_by(2).filter(|p| f(&p.to_luma())).count() as i32 > limit
|
||||
}
|
||||
|
||||
fn try_count_lines<'pixel>(
|
||||
iter: impl Iterator<Item = impl Iterator<Item = &'pixel Rgba<u8>>>,
|
||||
white: bool,
|
||||
limit: i32,
|
||||
) -> i32 {
|
||||
let mut count = 1;
|
||||
for line in iter {
|
||||
if line_not_filled_by(line, white, limit) {
|
||||
break;
|
||||
} else {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
count
|
||||
}
|
||||
|
||||
fn detect_border_lines<'pixel>(
|
||||
mut iter: impl Iterator<Item = impl Iterator<Item = &'pixel Rgba<u8>>>,
|
||||
line_len: u32,
|
||||
) -> i32 {
|
||||
let first_row = iter.next().expect("Image is empty!");
|
||||
let filled_limit = (line_len as f32 * FILLED_RATIO_LIMIT / 2.0).round() as i32;
|
||||
let (mut black, mut white) = (0, 0);
|
||||
for pixel in first_row.step_by(2) {
|
||||
let luma = &pixel.to_luma();
|
||||
if is_black(luma) {
|
||||
black += 1;
|
||||
} else if is_white(luma) {
|
||||
white += 1;
|
||||
}
|
||||
}
|
||||
match (black > filled_limit, white > filled_limit) {
|
||||
// Black is dominant color.
|
||||
(true, false) => try_count_lines(iter, false, filled_limit),
|
||||
// White is dominant color.
|
||||
(false, true) => try_count_lines(iter, true, filled_limit),
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn detect_border(image: &ImageBuffer<Rgba<u8>, &[u8]>) -> Option<[i32; 4]> {
|
||||
let (w, h) = image.dimensions();
|
||||
let top = detect_border_lines(image.rows(), w);
|
||||
let bottom = detect_border_lines(image.rows().rev(), w);
|
||||
let iter = (0..w).map(|row| (0..h).map(move |col| image.get_pixel(row, col)));
|
||||
let left = detect_border_lines(iter.clone(), h);
|
||||
let right = detect_border_lines(iter.rev(), h);
|
||||
Some([left, top, w as i32 - right, h as i32 - bottom])
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
#[jni_fn("com.hippo.ehviewer.image.ImageKt")]
|
||||
pub fn detectBorder(mut env: JNIEnv, _class: JClass, object: jobject) -> jintArray {
|
||||
jni_throwing(&mut env, |env| {
|
||||
let slice = with_bitmap_content(env, object, |img| Ok(detect_border(img)))?;
|
||||
let array = env.new_int_array(4)?;
|
||||
env.set_int_array_region(&array, 0, &slice.ok_or(anyhow!("Image too small!"))?)?;
|
||||
Ok(array.into_raw())
|
||||
})
|
||||
}
|
||||
@@ -58,8 +58,8 @@ where
|
||||
}
|
||||
|
||||
fn deref_mut_direct_bytebuffer<'local>(
|
||||
env: &'local JNIEnv,
|
||||
buffer: JByteBuffer,
|
||||
env: &JNIEnv,
|
||||
buffer: JByteBuffer<'local>,
|
||||
) -> Result<&'local mut [u8]> {
|
||||
let ptr = env.get_direct_buffer_address(&buffer)?;
|
||||
let cap = env.get_direct_buffer_capacity(&buffer)?;
|
||||
|
||||
Reference in new issue
Block a user