android: support writing png/jpg/webp via android.graphics.Bitmap

This commit is contained in:
rdb 2018-02-17 18:40:08 +01:00
parent 854d736882
commit 6bd1976892
13 changed files with 426 additions and 70 deletions

View File

@ -5100,7 +5100,8 @@ if (not RTDIST and not RUNTIME and PkgSkip("PVIEW")==0 and GetTarget() != 'andro
if (not RUNTIME and GetTarget() == 'android'):
OPTS=['DIR:panda/src/android']
TargetAdd('org/panda3d/android/NativeIStream.class', opts=OPTS, input='NativeIStream.java')
TargetAdd('org/panda3d/android/PandaActivity.class', opts=OPTS, input='PandaActivity.java', dep='org/panda3d/android/NativeIStream.class')
TargetAdd('org/panda3d/android/NativeOStream.class', opts=OPTS, input='NativeOStream.java')
TargetAdd('org/panda3d/android/PandaActivity.class', opts=OPTS, input='PandaActivity.java')
TargetAdd('p3android_composite1.obj', opts=OPTS, input='p3android_composite1.cxx')
TargetAdd('libp3android.dll', input='p3android_composite1.obj')

View File

@ -0,0 +1,52 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file NativeOStream.java
* @author rdb
* @date 2018-02-10
*/
package org.panda3d.android;
import java.io.OutputStream;
/**
* An implementation of OutputStream that puts its data into a C++ ostream
* pointer, passed as long.
*/
public class NativeOStream extends OutputStream {
private long streamPtr = 0;
public NativeOStream(long ptr) {
streamPtr = ptr;
}
@Override
public void flush() {
nativeFlush(streamPtr);
}
@Override
public void write(int b) {
nativePut(streamPtr, b);
}
@Override
public void write(byte[] buffer) {
nativeWrite(streamPtr, buffer, 0, buffer.length);
}
@Override
public void write(byte[] buffer, int offset, int length) {
nativeWrite(streamPtr, buffer, offset, length);
}
private static native void nativeFlush(long ptr);
private static native void nativePut(long ptr, int b);
private static native void nativeWrite(long ptr, byte[] buffer, int offset, int length);
}

View File

@ -20,12 +20,29 @@ import android.widget.Toast;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import org.panda3d.android.NativeIStream;
import org.panda3d.android.NativeOStream;
/**
* The entry point for a Panda-based activity. Loads the Panda libraries and
* also provides some utility functions.
*/
public class PandaActivity extends NativeActivity {
private static final Bitmap.Config sConfigs[] = {
null,
Bitmap.Config.ALPHA_8,
null,
Bitmap.Config.RGB_565,
Bitmap.Config.ARGB_4444,
Bitmap.Config.ARGB_8888,
null, //Bitmap.Config.RGBA_F16,
null, //Bitmap.Config.HARDWARE,
};
private static final Bitmap.CompressFormat sFormats[] = {
Bitmap.CompressFormat.JPEG,
Bitmap.CompressFormat.PNG,
Bitmap.CompressFormat.WEBP,
};
protected static BitmapFactory.Options readBitmapSize(long istreamPtr) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
@ -44,6 +61,15 @@ public class PandaActivity extends NativeActivity {
return BitmapFactory.decodeStream(stream, null, options);
}
protected static Bitmap createBitmap(int width, int height, int config, boolean hasAlpha) {
return Bitmap.createBitmap(width, height, sConfigs[config]);
}
protected static boolean compressBitmap(Bitmap bitmap, int format, int quality, long ostreamPtr) {
NativeOStream stream = new NativeOStream(ostreamPtr);
return bitmap.compress(sFormats[format], quality, stream);
}
protected static String getCurrentThreadName() {
return Thread.currentThread().getName();
}

View File

@ -24,12 +24,24 @@ struct android_app *panda_android_app = NULL;
jclass jni_PandaActivity;
jmethodID jni_PandaActivity_readBitmapSize;
jmethodID jni_PandaActivity_readBitmap;
jmethodID jni_PandaActivity_createBitmap;
jmethodID jni_PandaActivity_compressBitmap;
jmethodID jni_PandaActivity_showToast;
jclass jni_BitmapFactory_Options;
jfieldID jni_BitmapFactory_Options_outWidth;
jfieldID jni_BitmapFactory_Options_outHeight;
#ifndef HAVE_JPEG
static PNMFileTypeAndroid file_type_jpeg(PNMFileTypeAndroid::CF_jpeg);
#endif
#ifndef HAVE_PNG
static PNMFileTypeAndroid file_type_png(PNMFileTypeAndroid::CF_png);
#endif
#if __ANDROID_API__ >= 14
static PNMFileTypeAndroid file_type_webp(PNMFileTypeAndroid::CF_webp);
#endif
/**
* Initializes the library. This must be called at least once before any of
* the functions or classes in this library can be used. Normally, this is
@ -37,10 +49,6 @@ jfieldID jni_BitmapFactory_Options_outHeight;
*/
void
init_libandroid() {
PNMFileTypeRegistry *tr = PNMFileTypeRegistry::get_global_ptr();
PNMFileTypeAndroid::init_type();
PNMFileTypeAndroid::register_with_read_factory();
tr->register_type(new PNMFileTypeAndroid);
}
/**
@ -48,7 +56,7 @@ init_libandroid() {
* references and the method IDs.
*/
jint JNI_OnLoad(JavaVM *jvm, void *reserved) {
init_libandroid();
//init_libandroid();
Thread *thread = Thread::get_current_thread();
JNIEnv *env = thread->get_jni_env();
@ -63,6 +71,12 @@ jint JNI_OnLoad(JavaVM *jvm, void *reserved) {
jni_PandaActivity_readBitmap = env->GetStaticMethodID(jni_PandaActivity,
"readBitmap", "(JI)Landroid/graphics/Bitmap;");
jni_PandaActivity_createBitmap = env->GetStaticMethodID(jni_PandaActivity,
"createBitmap", "(IIIZ)Landroid/graphics/Bitmap;");
jni_PandaActivity_compressBitmap = env->GetStaticMethodID(jni_PandaActivity,
"compressBitmap", "(Landroid/graphics/Bitmap;IIJ)Z");
jni_PandaActivity_showToast = env->GetMethodID(jni_PandaActivity,
"showToast", "(Ljava/lang/String;I)V");
@ -72,6 +86,25 @@ jint JNI_OnLoad(JavaVM *jvm, void *reserved) {
jni_BitmapFactory_Options_outWidth = env->GetFieldID(jni_BitmapFactory_Options, "outWidth", "I");
jni_BitmapFactory_Options_outHeight = env->GetFieldID(jni_BitmapFactory_Options, "outHeight", "I");
nassertr(jni_PandaActivity_readBitmapSize, -1);
nassertr(jni_PandaActivity_readBitmap, -1);
nassertr(jni_PandaActivity_createBitmap, -1);
nassertr(jni_PandaActivity_compressBitmap, -1);
nassertr(jni_PandaActivity_showToast, -1);
// We put this in JNI_OnLoad because it relies on Java classes, which
// are only available when launched from the Java VM.
PNMFileTypeRegistry *tr = PNMFileTypeRegistry::get_global_ptr();
#ifndef HAVE_JPEG
tr->register_type(&file_type_jpeg);
#endif
#ifndef HAVE_PNG
tr->register_type(&file_type_png);
#endif
#if __ANDROID_API__ >= 14
tr->register_type(&file_type_webp);
#endif
return JNI_VERSION_1_4;
}
@ -86,6 +119,20 @@ void JNI_OnUnload(JavaVM *jvm, void *reserved) {
env->DeleteGlobalRef(jni_PandaActivity);
env->DeleteGlobalRef(jni_BitmapFactory_Options);
// These will no longer work without JNI, so unregister them.
PNMFileTypeRegistry *tr = PNMFileTypeRegistry::get_global_ptr();
if (tr != nullptr) {
#ifndef HAVE_JPEG
tr->unregister_type(&file_type_jpeg);
#endif
#ifndef HAVE_PNG
tr->unregister_type(&file_type_png);
#endif
#if __ANDROID_API__ >= 14
tr->unregister_type(&file_type_webp);
#endif
}
}
/**

View File

@ -31,6 +31,8 @@ extern EXPORT_CLASS struct android_app* panda_android_app;
extern jclass jni_PandaActivity;
extern jmethodID jni_PandaActivity_readBitmapHeader;
extern jmethodID jni_PandaActivity_readBitmap;
extern jmethodID jni_PandaActivity_createBitmap;
extern jmethodID jni_PandaActivity_compressBitmap;
extern jmethodID jni_PandaActivity_showToast;
extern jclass jni_BitmapFactory_Options;

View File

@ -0,0 +1,54 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file jni_NativeOStream.cxx
* @author rdb
* @date 2018-02-10
*/
#include <jni.h>
#include <ostream>
#if __GNUC__ >= 4
#define EXPORT_JNI extern "C" __attribute__((visibility("default")))
#else
#define EXPORT_JNI extern "C"
#endif
/**
* Flushes the stream.
*/
EXPORT_JNI void
Java_org_panda3d_android_NativeOStream_nativeFlush(JNIEnv *env, jclass clazz, jlong ptr) {
std::ostream *stream = (std::ostream *)ptr;
stream->flush();
}
/**
* Writes a single character to the ostream.
*/
EXPORT_JNI void
Java_org_panda3d_android_NativeOStream_nativePut(JNIEnv *env, jclass clazz, jlong ptr, int b) {
std::ostream *stream = (std::ostream *)ptr;
stream->put((char)(b & 0xff));
}
/**
* Writes an array of bytes to the ostream.
*/
EXPORT_JNI void
Java_org_panda3d_android_NativeOStream_nativeWrite(JNIEnv *env, jclass clazz, jlong ptr, jbyteArray byte_array, jint offset, jint length) {
std::ostream *stream = (std::ostream *)ptr;
jbyte *buffer = (jbyte *)alloca(length);
env->GetByteArrayRegion(byte_array, offset, length, buffer);
stream->write((char *)buffer, length);
}

View File

@ -1,4 +1,6 @@
#include "config_android.cxx"
#include "jni_NativeIStream.cxx"
#include "jni_NativeOStream.cxx"
#include "pnmFileTypeAndroid.cxx"
#include "pnmFileTypeAndroidReader.cxx"
#include "pnmFileTypeAndroidWriter.cxx"

View File

@ -17,24 +17,11 @@
#include "config_pnmimagetypes.h"
#include "pnmFileTypeRegistry.h"
#include "bamReader.h"
static const char * const extensions_android[] = {
"jpg", "jpeg", "gif", "png",
#if __ANDROID_API__ >= 14
"webp"
#endif
};
static const int num_extensions_android = sizeof(extensions_android) / sizeof(const char *);
TypeHandle PNMFileTypeAndroid::_type_handle;
/**
*
*/
PNMFileTypeAndroid::
PNMFileTypeAndroid() {
PNMFileTypeAndroid(CompressFormat format) : _format(format) {
}
/**
@ -51,7 +38,16 @@ get_name() const {
*/
int PNMFileTypeAndroid::
get_num_extensions() const {
return num_extensions_android;
switch (_format) {
case CF_jpeg:
return 3;
case CF_png:
return 1;
case CF_webp:
return 1;
default:
return 0;
}
}
/**
@ -60,8 +56,17 @@ get_num_extensions() const {
*/
string PNMFileTypeAndroid::
get_extension(int n) const {
nassertr(n >= 0 && n < num_extensions_android, string());
return extensions_android[n];
static const char *const jpeg_extensions[] = {"jpg", "jpeg", "jpe"};
switch (_format) {
case CF_jpeg:
return jpeg_extensions[n];
case CF_png:
return "png";
case CF_webp:
return "webp";
default:
return 0;
}
}
/**
@ -80,30 +85,17 @@ has_magic_number() const {
*/
PNMReader *PNMFileTypeAndroid::
make_reader(istream *file, bool owns_file, const string &magic_number) {
init_pnm();
return new Reader(this, file, owns_file, magic_number);
}
/**
* Registers the current object as something that can be read from a Bam file.
* Allocates and returns a new PNMWriter suitable for reading from this file
* type, if possible. If writing files of this type is not supported, returns
* NULL.
*/
void PNMFileTypeAndroid::
register_with_read_factory() {
BamReader::get_factory()->
register_factory(get_class_type(), make_PNMFileTypeAndroid);
}
/**
* This method is called by the BamReader when an object of this type is
* encountered in a Bam file; it should allocate and return a new object with
* all the data read.
*
* In the case of the PNMFileType objects, since these objects are all shared,
* we just pull the object from the registry.
*/
TypedWritable *PNMFileTypeAndroid::
make_PNMFileTypeAndroid(const FactoryParams &params) {
return PNMFileTypeRegistry::get_global_ptr()->get_type_by_handle(get_class_type());
PNMWriter *PNMFileTypeAndroid::
make_writer(ostream *file, bool owns_file) {
return new Writer(this, file, owns_file, _format);
}
#endif // ANDROID

View File

@ -30,7 +30,13 @@
*/
class EXPCL_PANDA_PNMIMAGETYPES PNMFileTypeAndroid : public PNMFileType {
public:
PNMFileTypeAndroid();
enum CompressFormat : jint {
CF_jpeg = 0,
CF_png = 1,
CF_webp = 2,
};
PNMFileTypeAndroid(CompressFormat format);
virtual string get_name() const;
@ -41,6 +47,7 @@ public:
virtual PNMReader *make_reader(istream *file, bool owns_file = true,
const string &magic_number = string());
virtual PNMWriter *make_writer(ostream *file, bool owns_file = true);
public:
class Reader : public PNMReader {
@ -60,29 +67,20 @@ public:
int32_t _format;
};
// The TypedWritable interface follows.
class Writer : public PNMWriter {
public:
static void register_with_read_factory();
Writer(PNMFileType *type, ostream *file, bool owns_file,
CompressFormat format);
protected:
static TypedWritable *make_PNMFileTypeAndroid(const FactoryParams &params);
public:
static TypeHandle get_class_type() {
return _type_handle;
}
static void init_type() {
PNMFileType::init_type();
register_type(_type_handle, "PNMFileTypeAndroid",
PNMFileType::get_class_type());
}
virtual TypeHandle get_type() const {
return get_class_type();
}
virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
virtual int write_data(xel *array, xelval *alpha);
virtual bool supports_grayscale() const;
private:
static TypeHandle _type_handle;
CompressFormat _format;
};
private:
CompressFormat _format;
};
#endif // ANDROID

View File

@ -0,0 +1,146 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file pnmFileTypeAndroidWriter.cxx
* @author rdb
* @date 2018-02-10
*/
#include "pnmFileTypeAndroid.h"
#ifdef ANDROID
#include "config_pnmimagetypes.h"
#include <android/bitmap.h>
#include <jni.h>
// See android/graphics/Bitmap.java
enum class BitmapConfig : jint {
ALPHA_8 = 1,
RGB_565 = 3,
ARGB_4444 = 4,
ARGB_8888 = 5,
RGBA_F16 = 6,
HARDWARE = 7,
};
/**
*
*/
PNMFileTypeAndroid::Writer::
Writer(PNMFileType *type, ostream *file, bool owns_file,
CompressFormat format) :
PNMWriter(type, file, owns_file),
_format(format)
{
}
/**
* Writes out an entire image all at once, including the header, based on the
* image data stored in the given _x_size * _y_size array and alpha pointers.
* (If the image type has no alpha channel, alpha is ignored.) Returns the
* number of rows correctly written.
*
* It is the user's responsibility to fill in the header data via calls to
* set_x_size(), set_num_channels(), etc., or copy_header_from(), before
* calling write_data().
*
* It is important to delete the PNMWriter class after successfully writing
* the data. Failing to do this may result in some data not getting flushed!
*
* Derived classes need not override this if they instead provide
* supports_streaming() and write_row(), below.
*/
int PNMFileTypeAndroid::Writer::
write_data(xel *array, xelval *alpha) {
size_t num_pixels = (size_t)_x_size * (size_t)_y_size;
Thread *current_thread = Thread::get_current_thread();
JNIEnv *env = current_thread->get_jni_env();
nassertr(env != nullptr, 0);
// Create a Bitmap object.
jobject bitmap =
env->CallStaticObjectMethod(jni_PandaActivity,
jni_PandaActivity_createBitmap,
(jint)_x_size, (jint)_y_size,
BitmapConfig::ARGB_8888,
(jboolean)has_alpha());
nassertr(bitmap != nullptr, 0);
// Get a writable pointer to write our pixel data to.
uint32_t *out;
int rc = AndroidBitmap_lockPixels(env, bitmap, (void **)&out);
if (rc != 0) {
android_cat.error()
<< "Could not lock bitmap pixels (result code " << rc << ")\n";
return 0;
}
if (_maxval == 255) {
if (has_alpha() && alpha != nullptr) {
for (size_t i = 0; i < num_pixels; ++i) {
out[i] = (array[i].r)
| (array[i].g << 8u)
| (array[i].b << 16u)
| (alpha[i] << 24u);
}
} else {
for (size_t i = 0; i < num_pixels; ++i) {
out[i] = (array[i].r)
| (array[i].g << 8u)
| (array[i].b << 16u)
| 0xff000000u;
}
}
} else {
double ratio = 255.0 / _maxval;
if (has_alpha() && alpha != nullptr) {
for (size_t i = 0; i < num_pixels; ++i) {
out[i] = ((uint32_t)(array[i].r * ratio))
| ((uint32_t)(array[i].g * ratio) << 8u)
| ((uint32_t)(array[i].b * ratio) << 16u)
| ((uint32_t)(alpha[i] * ratio) << 24u);
}
} else {
for (size_t i = 0; i < num_pixels; ++i) {
out[i] = ((uint32_t)(array[i].r * ratio))
| ((uint32_t)(array[i].g * ratio) << 8u)
| ((uint32_t)(array[i].b * ratio) << 16u)
| 0xff000000u;
}
}
}
// Finally, unlock the pixel data and compress it to the ostream.
AndroidBitmap_unlockPixels(env, bitmap);
jboolean res =
env->CallStaticBooleanMethod(jni_PandaActivity,
jni_PandaActivity_compressBitmap,
bitmap, _format, 85, (jlong)_file);
if (!res) {
android_cat.error()
<< "Failed to compress bitmap.\n";
return 0;
}
return _y_size;
}
/**
* Returns true if this particular PNMWriter understands grayscale images. If
* this is false, then the rgb values of the xel array will be pre-filled with
* the same value across all three channels, to allow the writer to simply
* write out RGB data for a grayscale image.
*/
bool PNMFileTypeAndroid::Writer::
supports_grayscale() const {
return false;
}
#endif // ANDROID

View File

@ -6,6 +6,7 @@
android:versionName="1.0">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-sdk android:minSdkVersion="21" />
<uses-feature android:glEsVersion="0x00020000" android:required="true" />

View File

@ -49,16 +49,19 @@ register_type(PNMFileType *type) {
}
// Make sure we haven't already registered this type.
Handles::iterator hi = _handles.find(type->get_type());
TypeHandle handle = type->get_type();
if (handle != PNMFileType::get_class_type()) {
Handles::iterator hi = _handles.find(handle);
if (hi != _handles.end()) {
pnmimage_cat->warning()
<< "Attempt to register PNMFileType " << type->get_name()
<< " (" << type->get_type() << ") more than once.\n";
return;
}
_handles.insert(Handles::value_type(handle, type));
}
_types.push_back(type);
_handles.insert(Handles::value_type(type->get_type(), type));
// Collect the unique extensions associated with the type.
pset<string> unique_extensions;
@ -82,6 +85,37 @@ register_type(PNMFileType *type) {
_requires_sort = true;
}
/**
* Removes a PNMFileType previously passed to register_type.
*/
void PNMFileTypeRegistry::
unregister_type(PNMFileType *type) {
if (pnmimage_cat->is_debug()) {
pnmimage_cat->debug()
<< "Unregistering image type " << type->get_name() << "\n";
}
TypeHandle handle = type->get_type();
if (handle != PNMFileType::get_class_type()) {
Handles::iterator hi = _handles.find(handle);
if (hi != _handles.end()) {
_handles.erase(hi);
}
}
_types.erase(std::remove(_types.begin(), _types.end(), type),
_types.end());
Extensions::iterator ei;
for (ei = _extensions.begin(); ei != _extensions.end(); ++ei) {
Types &types = ei->second;
types.erase(std::remove(types.begin(), types.end(), type),
types.end());
}
_requires_sort = true;
}
/**
* Returns the total number of types registered.
*/

View File

@ -33,6 +33,7 @@ public:
~PNMFileTypeRegistry();
void register_type(PNMFileType *type);
void unregister_type(PNMFileType *type);
PUBLISHED:
int get_num_types() const;