blob: 9d4844a8d09b91fc2953a90db529a877811502d9 [file] [log] [blame]
/*
* Copyright 2018 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "util.h"
#include <android/bitmap.h>
#include <unistd.h>
#include <sstream>
#include <string>
#include "jni_interface.h"
namespace augmented_image {
namespace util {
// Anonymous namespace for local static variables
namespace {
constexpr char kJniInterfaceClassName[] =
"com/google/ar/core/examples/c/augmentedimage/JniInterface";
constexpr char kLoadImageMethodName[] = "loadImage";
constexpr char kLoadImageMethodSignature[] =
"(Ljava/lang/String;)Landroid/graphics/Bitmap;";
constexpr char kLoadTextureMethodName[] = "loadTexture";
constexpr char kLoadTextureMethodSignature[] = "(ILandroid/graphics/Bitmap;)V";
constexpr char kHideFitToScanMethodName[] = "hideFitToScanImage";
constexpr char kHideFitToScanMethodSignature[] =
"(Lcom/google/ar/core/examples/c/augmentedimage/AugmentedImageActivity;)V";
static jclass jni_class_id = nullptr;
static jmethodID jni_load_image_method_id = nullptr;
static jmethodID jni_load_texture_method_id = nullptr;
static jmethodID jni_hide_fit_to_scan_method_id = nullptr;
} // namespace
void CheckGlError(const char* operation) {
GLint error = glGetError();
if (error) {
LOGE("after %s() glError (0x%x)\n", operation, error);
abort();
}
}
void InitializeJavaMethodIDs() {
JNIEnv* env = GetJniEnv();
jclass local_class_id = FindClass(kJniInterfaceClassName);
jni_class_id = static_cast<jclass>(env->NewGlobalRef(local_class_id));
jni_load_image_method_id =
env->GetStaticMethodID(jni_class_id,
kLoadImageMethodName,
kLoadImageMethodSignature);
jni_load_texture_method_id =
env->GetStaticMethodID(jni_class_id,
kLoadTextureMethodName,
kLoadTextureMethodSignature);
jni_hide_fit_to_scan_method_id = env->GetStaticMethodID(
jni_class_id, kHideFitToScanMethodName, kHideFitToScanMethodSignature);
}
void ReleaseJavaMethodIDs() {
JNIEnv* env = GetJniEnv();
env->DeleteGlobalRef(jni_class_id);
jni_class_id = nullptr;
jni_load_image_method_id = nullptr;
jni_load_texture_method_id = nullptr;
jni_hide_fit_to_scan_method_id = nullptr;
}
static jobject CallJavaLoadImage(jstring image_path) {
JNIEnv* env = GetJniEnv();
return env->CallStaticObjectMethod(jni_class_id, jni_load_image_method_id,
image_path);
}
static void CallJavaLoadTexture(int target, jobject image_obj) {
JNIEnv* env = GetJniEnv();
env->CallStaticVoidMethod(jni_class_id, jni_load_texture_method_id,
target, image_obj);
}
static void CallJavaHideFitToScanImage(jobject activity) {
JNIEnv* env = GetJniEnv();
env->CallStaticVoidMethod(jni_class_id, jni_hide_fit_to_scan_method_id,
activity);
}
// Convenience function used in CreateProgram below.
static GLuint LoadShader(GLenum shader_type, const char* shader_source) {
GLuint shader = glCreateShader(shader_type);
if (!shader) {
return shader;
}
glShaderSource(shader, 1, &shader_source, nullptr);
glCompileShader(shader);
GLint compiled = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (!compiled) {
GLint info_len = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &info_len);
if (!info_len) {
return shader;
}
char* buf = reinterpret_cast<char*>(malloc(info_len));
if (!buf) {
return shader;
}
glGetShaderInfoLog(shader, info_len, nullptr, buf);
LOGE("augmented_image::util::Could not compile shader %d:\n%s\n",
shader_type, buf);
free(buf);
glDeleteShader(shader);
shader = 0;
}
return shader;
}
GLuint CreateProgram(AAssetManager* mgr, const char* vertex_shader_file_name,
const char* fragment_shader_file_name) {
std::string VertexShaderContent;
if (!LoadFileFromAssetManager(mgr, vertex_shader_file_name,
&VertexShaderContent)) {
LOGE("Failed to load file: %s", vertex_shader_file_name);
return 0;
}
std::string FragmentShaderContent;
if (!LoadFileFromAssetManager(mgr, fragment_shader_file_name,
&FragmentShaderContent)) {
LOGE("Failed to load file: %s", fragment_shader_file_name);
return 0;
}
GLuint vertexShader =
LoadShader(GL_VERTEX_SHADER, VertexShaderContent.c_str());
if (!vertexShader) {
return 0;
}
GLuint fragment_shader =
LoadShader(GL_FRAGMENT_SHADER, FragmentShaderContent.c_str());
if (!fragment_shader) {
return 0;
}
GLuint program = glCreateProgram();
if (program) {
glAttachShader(program, vertexShader);
CheckGlError("augmented_image::util::glAttachShader");
glAttachShader(program, fragment_shader);
CheckGlError("augmented_image::util::glAttachShader");
glLinkProgram(program);
GLint link_status = GL_FALSE;
glGetProgramiv(program, GL_LINK_STATUS, &link_status);
if (link_status != GL_TRUE) {
GLint buf_length = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &buf_length);
if (buf_length) {
char* buf = reinterpret_cast<char*>(malloc(buf_length));
if (buf) {
glGetProgramInfoLog(program, buf_length, nullptr, buf);
LOGE("augmented_image::util::Could not link program:\n%s\n", buf);
free(buf);
}
}
glDeleteProgram(program);
program = 0;
}
}
return program;
}
bool LoadFileFromAssetManager(AAssetManager* mgr, const char* file_name,
std::string* out_file_text_string) {
// If the file hasn't been uncompressed, load it to the internal storage.
// Note that AAsset_openFileDescriptor doesn't support compressed
// files (.obj).
AAsset* asset = AAssetManager_open(mgr, file_name, AASSET_MODE_STREAMING);
if (asset == nullptr) {
LOGE("Error opening asset %s", file_name);
return false;
}
off_t file_size = AAsset_getLength(asset);
out_file_text_string->resize(file_size);
int ret = AAsset_read(asset, &out_file_text_string->front(), file_size);
if (ret <= 0) {
LOGE("Failed to open file: %s", file_name);
AAsset_close(asset);
return false;
}
AAsset_close(asset);
return true;
}
bool HideFitToScanImage(void* activity) {
jobject activity_obj = static_cast<jobject>(activity);
CallJavaHideFitToScanImage(activity_obj);
return true;
}
bool LoadPngFromAssetManager(int target, const std::string& path) {
JNIEnv* env = GetJniEnv();
jstring j_path = env->NewStringUTF(path.c_str());
jobject image_obj = CallJavaLoadImage(j_path);
env->DeleteLocalRef(j_path);
CallJavaLoadTexture(target, image_obj);
return true;
}
bool LoadImageFromAssetManager(const std::string& path, int* out_width,
int* out_height, int* out_stride,
uint8_t** out_pixel_buffer) {
JNIEnv* env = GetJniEnv();
jstring j_path = env->NewStringUTF(path.c_str());
jobject image_obj = CallJavaLoadImage(j_path);
env->DeleteLocalRef(j_path);
// image_obj contains a Bitmap Java object.
AndroidBitmapInfo bitmap_info;
AndroidBitmap_getInfo(env, image_obj, &bitmap_info);
// Attention: We are only going to support RGBA_8888 format in this sample.
CHECK(bitmap_info.format == ANDROID_BITMAP_FORMAT_RGBA_8888);
*out_width = bitmap_info.width;
*out_height = bitmap_info.height;
*out_stride = bitmap_info.stride;
void* jvm_buffer = nullptr;
CHECK(AndroidBitmap_lockPixels(env, image_obj, &jvm_buffer) ==
ANDROID_BITMAP_RESULT_SUCCESS);
// Copy jvm_buffer_address to pixel_buffer_address
int32_t total_size_in_byte = bitmap_info.stride * bitmap_info.width;
*out_pixel_buffer = new uint8_t[total_size_in_byte];
memcpy(*out_pixel_buffer, jvm_buffer, total_size_in_byte);
// release jvm_buffer back to JVM
CHECK(AndroidBitmap_unlockPixels(env, image_obj) ==
ANDROID_BITMAP_RESULT_SUCCESS);
return true;
}
bool LoadObjFile(AAssetManager* mgr, const std::string& file_name,
std::vector<GLfloat>* out_vertices,
std::vector<GLfloat>* out_normals,
std::vector<GLfloat>* out_uv,
std::vector<GLushort>* out_indices) {
std::vector<GLfloat> temp_positions;
std::vector<GLfloat> temp_normals;
std::vector<GLfloat> temp_uvs;
std::vector<GLushort> vertex_indices;
std::vector<GLushort> normal_indices;
std::vector<GLushort> uv_indices;
std::string file_buffer;
bool read_success =
LoadFileFromAssetManager(mgr, file_name.c_str(), &file_buffer);
if (!read_success) {
return false;
}
std::stringstream file_string_stream(file_buffer);
while (!file_string_stream.eof()) {
char line_header[128];
file_string_stream.getline(line_header, 128);
if (line_header[0] == 'v' && line_header[1] == 'n') {
// Parse vertex normal.
GLfloat normal[3];
int matches = sscanf(line_header, "vn %f %f %f\n", &normal[0], &normal[1],
&normal[2]);
if (matches != 3) {
LOGE("Format of 'vn float float float' required for each normal line");
return false;
}
temp_normals.push_back(normal[0]);
temp_normals.push_back(normal[1]);
temp_normals.push_back(normal[2]);
} else if (line_header[0] == 'v' && line_header[1] == 't') {
// Parse texture uv.
GLfloat uv[2];
int matches = sscanf(line_header, "vt %f %f\n", &uv[0], &uv[1]);
if (matches != 2) {
LOGE("Format of 'vt float float' required for each texture uv line");
return false;
}
temp_uvs.push_back(uv[0]);
temp_uvs.push_back(uv[1]);
} else if (line_header[0] == 'v') {
// Parse vertex.
GLfloat vertex[3];
int matches = sscanf(line_header, "v %f %f %f\n", &vertex[0], &vertex[1],
&vertex[2]);
if (matches != 3) {
LOGE("Format of 'v float float float' required for each vertice line");
return false;
}
temp_positions.push_back(vertex[0]);
temp_positions.push_back(vertex[1]);
temp_positions.push_back(vertex[2]);
} else if (line_header[0] == 'f') {
// Actual faces information starts from the second character.
char* face_line = &line_header[1];
unsigned int vertex_index[4];
unsigned int normal_index[4];
unsigned int texture_index[4];
std::vector<char*> per_vert_info_list;
char* per_vert_info_list_c_str;
char* face_line_iter = face_line;
while ((per_vert_info_list_c_str =
strtok_r(face_line_iter, " ", &face_line_iter))) {
// Divide each faces information into individual positions.
per_vert_info_list.push_back(per_vert_info_list_c_str);
}
bool is_normal_available = false;
bool is_uv_available = false;
for (int i = 0; i < per_vert_info_list.size(); ++i) {
char* per_vert_info;
int per_vert_infor_count = 0;
bool is_vertex_normal_only_face =
(strstr(per_vert_info_list[i], "//") != nullptr);
char* per_vert_info_iter = per_vert_info_list[i];
while ((per_vert_info =
strtok_r(per_vert_info_iter, "/", &per_vert_info_iter))) {
// write only normal and vert values.
switch (per_vert_infor_count) {
case 0:
// Write to vertex indices.
vertex_index[i] = atoi(per_vert_info); // NOLINT
break;
case 1:
// Write to texture indices.
if (is_vertex_normal_only_face) {
normal_index[i] = atoi(per_vert_info); // NOLINT
is_normal_available = true;
} else {
texture_index[i] = atoi(per_vert_info); // NOLINT
is_uv_available = true;
}
break;
case 2:
// Write to normal indices.
if (!is_vertex_normal_only_face) {
normal_index[i] = atoi(per_vert_info); // NOLINT
is_normal_available = true;
break;
}
[[clang::fallthrough]];
// Intentionally falling to default error case because vertex
// normal face only has two values.
default:
// Error formatting.
LOGE(
"Format of 'f int/int/int int/int/int int/int/int "
"(int/int/int)' "
"or 'f int//int int//int int//int (int//int)' required for "
"each face");
return false;
}
per_vert_infor_count++;
}
}
int vertices_count = per_vert_info_list.size();
for (int i = 2; i < vertices_count; ++i) {
vertex_indices.push_back(vertex_index[0] - 1);
vertex_indices.push_back(vertex_index[i - 1] - 1);
vertex_indices.push_back(vertex_index[i] - 1);
if (is_normal_available) {
normal_indices.push_back(normal_index[0] - 1);
normal_indices.push_back(normal_index[i - 1] - 1);
normal_indices.push_back(normal_index[i] - 1);
}
if (is_uv_available) {
uv_indices.push_back(texture_index[0] - 1);
uv_indices.push_back(texture_index[i - 1] - 1);
uv_indices.push_back(texture_index[i] - 1);
}
}
}
}
bool is_normal_available = (!normal_indices.empty());
bool is_uv_available = (!uv_indices.empty());
if (is_normal_available && normal_indices.size() != vertex_indices.size()) {
LOGE("Obj normal indices does not equal to vertex indices.");
return false;
}
if (is_uv_available && uv_indices.size() != vertex_indices.size()) {
LOGE("Obj UV indices does not equal to vertex indices.");
return false;
}
for (unsigned int i = 0; i < vertex_indices.size(); i++) {
unsigned int vertex_index = vertex_indices[i];
out_vertices->push_back(temp_positions[vertex_index * 3]);
out_vertices->push_back(temp_positions[vertex_index * 3 + 1]);
out_vertices->push_back(temp_positions[vertex_index * 3 + 2]);
out_indices->push_back(i);
if (is_normal_available) {
unsigned int normal_index = normal_indices[i];
out_normals->push_back(temp_normals[normal_index * 3]);
out_normals->push_back(temp_normals[normal_index * 3 + 1]);
out_normals->push_back(temp_normals[normal_index * 3 + 2]);
}
if (is_uv_available) {
unsigned int uv_index = uv_indices[i];
out_uv->push_back(temp_uvs[uv_index * 2]);
out_uv->push_back(temp_uvs[uv_index * 2 + 1]);
}
}
return true;
}
void Log4x4Matrix(float raw_matrix[16]) {
LOGI(
"%f, %f, %f, %f\n"
"%f, %f, %f, %f\n"
"%f, %f, %f, %f\n"
"%f, %f, %f, %f\n",
raw_matrix[0], raw_matrix[1], raw_matrix[2], raw_matrix[3], raw_matrix[4],
raw_matrix[5], raw_matrix[6], raw_matrix[7], raw_matrix[8], raw_matrix[9],
raw_matrix[10], raw_matrix[11], raw_matrix[12], raw_matrix[13],
raw_matrix[14], raw_matrix[15]);
}
void GetTransformMatrixFromAnchor(const ArSession* ar_session,
const ArAnchor* ar_anchor,
glm::mat4* out_model_mat) {
if (out_model_mat == nullptr) {
LOGE("util::GetTransformMatrixFromAnchor model_mat is null.");
return;
}
util::ScopedArPose pose(ar_session);
ArAnchor_getPose(ar_session, ar_anchor, pose.GetArPose());
ArPose_getMatrix(ar_session, pose.GetArPose(),
glm::value_ptr(*out_model_mat));
}
void ConvertRgbaToGrayscale(const uint8_t* image_pixel_buffer, int32_t width,
int32_t height, int32_t stride,
uint8_t** out_grayscale_buffer) {
int32_t grayscale_stride = stride / 4; // Only support RGBA_8888 format
uint8_t* grayscale_buffer = new uint8_t[grayscale_stride * height];
for (int h = 0; h < height; ++h) {
for (int w = 0; w < width; ++w) {
const uint8_t* pixel = &image_pixel_buffer[w * 4 + h * stride];
uint8_t r = *pixel;
uint8_t g = *(pixel + 1);
uint8_t b = *(pixel + 2);
grayscale_buffer[w + h * grayscale_stride] =
static_cast<uint8_t>(0.213f * r + 0.715 * g + 0.072 * b);
}
}
*out_grayscale_buffer = grayscale_buffer;
}
} // namespace util
} // namespace augmented_image