Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Commit

Permalink
[android] use native image encoding and decoding
Browse files Browse the repository at this point in the history
  • Loading branch information
kkaefer committed Jan 27, 2017
1 parent 14d5c33 commit 4d35826
Show file tree
Hide file tree
Showing 10 changed files with 278 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1604,18 +1604,8 @@ public void setOnMyBearingTrackingModeChangeListener(@Nullable OnMyBearingTracki
* @param bitmap A pre-allocated bitmap.
*/
@UiThread
public void snapshot(@NonNull SnapshotReadyCallback callback, @Nullable final Bitmap bitmap) {
nativeMapView.addSnapshotCallback(callback, bitmap);
}

/**
* Takes a snapshot of the map.
*
* @param callback Callback method invoked when the snapshot is taken.
*/
@UiThread
public void snapshot(@NonNull SnapshotReadyCallback callback) {
snapshot(callback, null);
nativeMapView.addSnapshotCallback(callback);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.PointF;
import android.graphics.RectF;
import android.os.Build;
Expand Down Expand Up @@ -54,7 +53,7 @@ final class NativeMapView {
private CopyOnWriteArrayList<MapView.OnMapChangedListener> onMapChangedListeners;

// Listener invoked to return a bitmap of the map
private SnapshotRequest snapshotRequest;
private MapboxMap.SnapshotReadyCallback snapshotReadyCallback;

//
// Static methods
Expand Down Expand Up @@ -935,18 +934,9 @@ protected void onFpsChanged(double fps) {
mapView.onFpsChanged(fps);
}

protected void onSnapshotReady(byte[] bytes) {
if (snapshotRequest != null && bytes != null) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inBitmap = snapshotRequest.getBitmap(); // the old Bitmap to be reused
options.inMutable = true;
options.inSampleSize = 1;
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);

MapboxMap.SnapshotReadyCallback callback = snapshotRequest.getCallback();
if (callback != null) {
callback.onSnapshotReady(bitmap);
}
protected void onSnapshotReady(Bitmap bitmap) {
if (snapshotReadyCallback != null && bitmap != null) {
snapshotReadyCallback.onSnapshotReady(bitmap);
}
}

Expand Down Expand Up @@ -1187,27 +1177,9 @@ void removeOnMapChangedListener(@NonNull MapView.OnMapChangedListener listener)
// Snapshot
//

void addSnapshotCallback(@NonNull MapboxMap.SnapshotReadyCallback callback, @Nullable Bitmap bitmap) {
snapshotRequest = new SnapshotRequest(bitmap, callback);
void addSnapshotCallback(@NonNull MapboxMap.SnapshotReadyCallback callback) {
snapshotReadyCallback = callback;
scheduleTakeSnapshot();
render();
}

private static class SnapshotRequest {
private Bitmap bitmap;
private MapboxMap.SnapshotReadyCallback callback;

SnapshotRequest(Bitmap bitmap, MapboxMap.SnapshotReadyCallback callback) {
this.bitmap = bitmap;
this.callback = callback;
}

public Bitmap getBitmap() {
return bitmap;
}

public MapboxMap.SnapshotReadyCallback getCallback() {
return callback;
}
}
}
13 changes: 6 additions & 7 deletions platform/android/config.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ if ((ANDROID_ABI STREQUAL "armeabi") OR (ANDROID_ABI STREQUAL "armeabi-v7a") OR
endif()

mason_use(jni.hpp VERSION 2.0.0 HEADER_ONLY)
mason_use(libjpeg-turbo VERSION 1.5.0)
mason_use(libpng VERSION 1.6.25)
mason_use(libzip VERSION 1.1.3)
mason_use(nunicode VERSION 1.7.1)
mason_use(sqlite VERSION 3.14.2)
Expand Down Expand Up @@ -68,10 +66,12 @@ macro(mbgl_platform_core)
PRIVATE platform/default/utf.cpp

# Image handling
PRIVATE platform/default/image.cpp
PRIVATE platform/default/png_writer.cpp
PRIVATE platform/default/png_reader.cpp
PRIVATE platform/default/jpeg_reader.cpp
PRIVATE platform/android/src/bitmap.cpp
PRIVATE platform/android/src/bitmap.hpp
PRIVATE platform/android/src/bitmap_factory.cpp
PRIVATE platform/android/src/bitmap_factory.hpp
PRIVATE platform/android/src/image.cpp

# Thread pool
PRIVATE platform/default/mbgl/util/default_thread_pool.cpp
Expand Down Expand Up @@ -147,8 +147,6 @@ macro(mbgl_platform_core)

target_add_mason_package(mbgl-core PUBLIC sqlite)
target_add_mason_package(mbgl-core PUBLIC nunicode)
target_add_mason_package(mbgl-core PUBLIC libpng)
target_add_mason_package(mbgl-core PUBLIC libjpeg-turbo)
target_add_mason_package(mbgl-core PUBLIC libzip)
target_add_mason_package(mbgl-core PUBLIC geojson)
target_add_mason_package(mbgl-core PUBLIC jni.hpp)
Expand All @@ -165,6 +163,7 @@ macro(mbgl_platform_core)
target_link_libraries(mbgl-core
PUBLIC -llog
PUBLIC -landroid
PUBLIC -ljnigraphics
PUBLIC -lEGL
PUBLIC -lGLESv2
PUBLIC -lstdc++
Expand Down
132 changes: 132 additions & 0 deletions platform/android/src/bitmap.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
#include "bitmap.hpp"

#include <android/bitmap.h>

namespace mbgl {
namespace android {

class PixelGuard {
public:
PixelGuard(jni::JNIEnv& env_, jni::Object<Bitmap> bitmap_) : env(env_), bitmap(bitmap_) {
const int result = AndroidBitmap_lockPixels(&env, jni::Unwrap(*bitmap),
reinterpret_cast<void**>(&address));
if (result != ANDROID_BITMAP_RESULT_SUCCESS) {
throw std::runtime_error("bitmap decoding: could not lock pixels");
}
}
~PixelGuard() {
const int result = AndroidBitmap_unlockPixels(&env, jni::Unwrap(*bitmap));
if (result != ANDROID_BITMAP_RESULT_SUCCESS) {
throw std::runtime_error("bitmap decoding: could not unlock pixels");
}
}

auto* get() {
return address;
}

const auto* get() const {
return address;
}

private:
jni::JNIEnv& env;
jni::Object<Bitmap> bitmap;
uint8_t* address;
};

void Bitmap::Config::registerNative(jni::JNIEnv& env) {
_class = *jni::Class<Config>::Find(env).NewGlobalRef(env).release();
}

jni::Class<Bitmap::Config> Bitmap::Config::_class;

jni::Object<Bitmap::Config> Bitmap::Config::Create(jni::JNIEnv& env, Value value) {
switch (value) {
case ALPHA_8:
return _class.Get(env,
jni::StaticField<Config, jni::Object<Config>>(env, _class, "ALPHA_8"));
case ARGB_4444:
return _class.Get(env,
jni::StaticField<Config, jni::Object<Config>>(env, _class, "ARGB_4444"));
case ARGB_8888:
return _class.Get(env,
jni::StaticField<Config, jni::Object<Config>>(env, _class, "ARGB_8888"));
case RGB_565:
return _class.Get(env,
jni::StaticField<Config, jni::Object<Config>>(env, _class, "RGB_565"));
default:
throw std::runtime_error("invalid enum value for Bitmap.Config");
}
}

void Bitmap::registerNative(jni::JNIEnv& env) {
_class = *jni::Class<Bitmap>::Find(env).NewGlobalRef(env).release();
Config::registerNative(env);
}

jni::Class<Bitmap> Bitmap::_class;

jni::Object<Bitmap> Bitmap::CreateBitmap(jni::JNIEnv& env,
jni::jint width,
jni::jint height,
jni::Object<Config> config) {
using Signature = jni::Object<Bitmap>(jni::jint, jni::jint, jni::Object<Config>);
auto method = _class.GetStaticMethod<Signature>(env, "createBitmap");
return _class.Call(env, method, width, height, config);
}

jni::Object<Bitmap> Bitmap::CreateBitmap(jni::JNIEnv& env, const PremultipliedImage& image) {
auto bitmap = CreateBitmap(env, image.size.width, image.size.height, Config::ARGB_8888);

AndroidBitmapInfo info;
const int result = AndroidBitmap_getInfo(&env, jni::Unwrap(*bitmap), &info);
if (result != ANDROID_BITMAP_RESULT_SUCCESS) {
// TODO: more specific information
throw std::runtime_error("bitmap creation: couldn't get bitmap info");
}

assert(info.width == image.size.width);
assert(info.height == image.size.height);
assert(info.format == ANDROID_BITMAP_FORMAT_RGBA_8888);

PixelGuard guard(env, bitmap);

// Copy the PremultipliedImage into the Android Bitmap
for (uint32_t y = 0; y < image.size.height; y++) {
auto begin = image.data.get() + y * image.stride();
std::copy(begin, begin + image.stride(), guard.get() + y * info.stride);
}

return bitmap;
}

PremultipliedImage Bitmap::GetImage(jni::JNIEnv& env, jni::Object<Bitmap> bitmap) {
AndroidBitmapInfo info;
const int result = AndroidBitmap_getInfo(&env, jni::Unwrap(*bitmap), &info);
if (result != ANDROID_BITMAP_RESULT_SUCCESS) {
// TODO: more specific information
throw std::runtime_error("bitmap decoding: couldn't get bitmap info");
}

if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
// TODO: convert
throw std::runtime_error("bitmap decoding: bitmap format invalid");
}

const PixelGuard guard(env, bitmap);

// Copy the Android Bitmap into the PremultipliedImage.
auto pixels =
std::make_unique<uint8_t[]>(info.width * info.height * PremultipliedImage::channels);
for (uint32_t y = 0; y < info.height; y++) {
auto begin = guard.get() + y * info.stride;
std::copy(begin, begin + info.width * PremultipliedImage::channels,
pixels.get() + y * info.width * PremultipliedImage::channels);
}

return { Size{ info.width, info.height }, std::move(pixels) };
}

} // namespace android
} // namespace mbgl
52 changes: 52 additions & 0 deletions platform/android/src/bitmap.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#pragma once

#include <mbgl/util/image.hpp>

#include <jni/jni.hpp>

namespace mbgl {
namespace android {

class Bitmap {
public:
class Config {
public:
static constexpr auto Name() {
return "android/graphics/Bitmap$Config";
};
static void registerNative(jni::JNIEnv&);

enum Value {
ALPHA_8,
ARGB_4444,
ARGB_8888,
RGB_565,
};

static jni::Object<Config> Create(jni::JNIEnv&, Value);

private:
static jni::Class<Config> _class;
};

static constexpr auto Name() {
return "android/graphics/Bitmap";
};
static void registerNative(jni::JNIEnv&);

static jni::Object<Bitmap>
CreateBitmap(jni::JNIEnv&, jni::jint width, jni::jint height, jni::Object<Config>);
static jni::Object<Bitmap>
CreateBitmap(jni::JNIEnv& env, jni::jint width, jni::jint height, Config::Value config) {
return CreateBitmap(env, width, height, Config::Create(env, config));
}

static PremultipliedImage GetImage(jni::JNIEnv&, jni::Object<Bitmap>);
static jni::Object<Bitmap> CreateBitmap(jni::JNIEnv&, const PremultipliedImage&);

private:
static jni::Class<Bitmap> _class;
};

} // namespace android
} // namespace mbgl
25 changes: 25 additions & 0 deletions platform/android/src/bitmap_factory.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#include "bitmap_factory.hpp"

namespace mbgl {
namespace android {

void BitmapFactory::registerNative(jni::JNIEnv& env) {
_class = *jni::Class<BitmapFactory>::Find(env).NewGlobalRef(env).release();
}

jni::Class<BitmapFactory> BitmapFactory::_class;

jni::Object<Bitmap> BitmapFactory::DecodeByteArray(jni::JNIEnv& env,
jni::Array<jni::jbyte> data,
jni::jint offset,
jni::jint length) {

// Images are loaded with ARGB_8888 config, and premultiplied by default, which is exactly
// what we want, so we're not providing a BitmapFactory.Options object.
using Signature = jni::Object<Bitmap>(jni::Array<jni::jbyte>, jni::jint, jni::jint);
auto method = _class.GetStaticMethod<Signature>(env, "decodeByteArray");
return _class.Call(env, method, data, offset, length);
}

} // namespace android
} // namespace mbgl
25 changes: 25 additions & 0 deletions platform/android/src/bitmap_factory.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#pragma once

#include <jni/jni.hpp>

#include "bitmap.hpp"

namespace mbgl {
namespace android {

class BitmapFactory {
public:
static constexpr auto Name() {
return "android/graphics/BitmapFactory";
};
static void registerNative(jni::JNIEnv&);

static jni::Object<Bitmap>
DecodeByteArray(jni::JNIEnv&, jni::Array<jni::jbyte> data, jni::jint offset, jni::jint length);

private:
static jni::Class<BitmapFactory> _class;
};

} // namespace android
} // namespace mbgl
22 changes: 22 additions & 0 deletions platform/android/src/image.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#include <mbgl/util/image.hpp>
#include <mbgl/util/string.hpp>

#include <string>

#include "attach_env.hpp"
#include "bitmap_factory.hpp"

namespace mbgl {

PremultipliedImage decodeImage(const std::string& string) {
auto env{ android::AttachEnv() };

auto array = jni::Array<jni::jbyte>::New(*env, string.size());
jni::SetArrayRegion(*env, *array, 0, string.size(),
reinterpret_cast<const signed char*>(string.data()));

auto bitmap = android::BitmapFactory::DecodeByteArray(*env, array, 0, string.size());
return android::Bitmap::GetImage(*env, bitmap);
}

} // namespace mbgl
Loading

4 comments on commit 4d35826

@jaegs
Copy link
Contributor

@jaegs jaegs commented on 4d35826 Jan 23, 2018

Choose a reason for hiding this comment

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

@kkaefer - could Bitmap::GetImage be used for NativeMapView.addImage too?

@kkaefer
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jaegs say more? It already uses the Android image decoding routines.

@jaegs
Copy link
Contributor

@jaegs jaegs commented on 4d35826 Jan 23, 2018

Choose a reason for hiding this comment

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

Bitmaps are stored in native memory so currently NativeMapView.addImage is copying the bitmap into Java and then sending it back to native whereas Bitmap::GetImage does a direct native-to-native copy. I tested using Bitmap::GetImage and it saves around .3ms for each of the some small icons I tried on a Pixel phone. I'll make a PR.

@kkaefer
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, understood. Thanks!

Please sign in to comment.