Merge native

This commit is contained in:
tarsin committed 2022-08-03 17:48:46 +08:00
1 parent a8023f7334
commit 6f3bd92539
31 files changed
+28 -62

No files matched your search

+34
View File
@@ -0,0 +1,34 @@
/*
* Copyright 2022 Tarsin Norbin
*
* This file is part of EhViewer
*
* EhViewer is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* EhViewer is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with EhViewer.
* If not, see <https://www.gnu.org/licenses/>.
*/
package com.hippo;
public class Native {
public static void initialize() {
System.loadLibrary("ehviewer");
}
public static native String getlibarchiveVersion();
public static native String getliblzmaVersion();
public static native String getzlibVersion();
public static native String getnettleVersion();
}
@@ -0,0 +1,64 @@
/*
* Copyright 2022 Tarsin Norbin
*
* This file is part of EhViewer
*
* EhViewer is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* EhViewer is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with EhViewer.
* If not, see <https://www.gnu.org/licenses/>.
*/
package com.hippo;
import android.content.Context;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.system.Os;
import android.system.OsConstants;
import java.io.FileDescriptor;
public class UriArchiveAccessor {
ParcelFileDescriptor pfd;
FileDescriptor fd;
long archiveAddr;
Context ctx;
Uri uri;
public UriArchiveAccessor(Context ctx, Uri uri) {
this.ctx = ctx;
this.uri = uri;
}
public int open() throws Exception {
pfd = ctx.getContentResolver().openFileDescriptor(uri, "r");
fd = pfd.getFileDescriptor();
archiveAddr = Os.mmap(0, pfd.getStatSize(), OsConstants.PROT_READ, OsConstants.MAP_PRIVATE, fd, 0);
return openArchive(archiveAddr, pfd.getStatSize());
}
private native int openArchive(long addr, long size);
public native long extracttoOutputStream(int index);
public native boolean needPassword();
public native boolean providePassword(String str);
private native void closeArchive();
public void close() throws Exception {
closeArchive();
Os.munmap(archiveAddr, pfd.getStatSize());
Os.close(fd);
pfd.close();
}
}
@@ -0,0 +1,215 @@
/*
* Copyright 2016 Hippo Seven
*
* 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.
*/
package com.hippo.image;
import android.graphics.Bitmap;
import androidx.annotation.Nullable;
import java.io.FileDescriptor;
import java.util.ArrayList;
/**
* The {@code Image} is a image which stored pixel data in native heap
*/
public final class Image {
public static final int FORMAT_NORMAL = 0;
public static final int FORMAT_ANIMATED = 1;
private static final ArrayList<Image> imageList = new ArrayList<>();
private static boolean needEvictAll = false;
private final int mFormat;
private final int mWidth;
private final int mHeight;
private long mNativePtr;
private Image(long nativePtr, int format, int width, int height) {
if (needEvictAll)
evictAll();
mNativePtr = nativePtr;
mFormat = format;
mWidth = width;
mHeight = height;
imageList.add(this);
}
public static void lazyEvictAll() {
needEvictAll = true;
}
private static void evictAll() {
(new ArrayList<>(imageList)).forEach(Image::recycle);
needEvictAll = false;
}
/**
* Decode image from {@code InputStream}
*/
@Nullable
public static Image decode(FileDescriptor fd, boolean partially) {
return nativeDecode(fd);
}
/**
* Decode image from {@code InputStream}
*/
@Nullable
public static Image decode(Integer fd, boolean partially) {
return nativeDecodeFdInt(fd);
}
/**
* Decode image from {@code InputStream}
*/
@Nullable
public static Image decodeAddr(Long addr, boolean partially) {
return nativeDecodeAddr(addr);
}
/**
* Create a plain image from Bitmap
*/
@Nullable
public static Image create(Bitmap bitmap) {
return nativeCreate(bitmap);
}
/**
* Return all un-recycled {@code Image} instance count.
* It is useful for debug.
*/
public static int getImageCount() {
return imageList.size();
}
private static native Image nativeDecode(FileDescriptor fd);
private static native Image nativeDecodeFdInt(int fd);
private static native Image nativeDecodeAddr(long addr);
private static native Image nativeCreate(Bitmap bitmap);
private static native void nativeRender(long nativePtr,
int srcX, int srcY, Bitmap dst, int dstX, int dstY,
int width, int height);
private static native void nativeTexImage(long nativePtr, boolean init, int offsetX, int offsetY, int width, int height);
private static native void nativeAdvance(long nativePtr);
private static native int nativeGetDelay(long nativePtr);
private static native boolean nativeIsOpaque(long nativePtr);
private static native void nativeRecycle(long nativePtr);
/**
* Return the format of the image
*/
public int getFormat() {
return mFormat;
}
/**
* Return the width of the image
*/
public int getWidth() {
return mWidth;
}
/**
* Return the height of the image
*/
public int getHeight() {
return mHeight;
}
private void checkRecycled() {
if (mNativePtr == 0) {
throw new IllegalStateException("The image is recycled.");
}
}
/**
* Render the image to {@code Bitmap}
*/
public void render(int srcX, int srcY, Bitmap dst, int dstX, int dstY,
int width, int height, boolean fillBlank, int defaultColor) {
checkRecycled();
nativeRender(mNativePtr, srcX, srcY, dst, dstX, dstY, width, height);
}
/**
* Call {@code glTexImage2D} for init is true and
* call {@code glTexSubImage2D} for init is false.
* width * height must <= 512 * 512 or do nothing
*/
public void texImage(boolean init, int offsetX, int offsetY, int width, int height) {
checkRecycled();
nativeTexImage(mNativePtr, init, offsetX, offsetY, width, height);
}
/**
* Move to next frame. Do nothing for non-animation image
*/
public void advance() {
checkRecycled();
nativeAdvance(mNativePtr);
}
/**
* Return current frame delay. 0 for non-animation image
*/
public int getDelay() {
checkRecycled();
int delay = nativeGetDelay(mNativePtr);
return delay <= 10 ? 100 : delay;
}
/**
* Return is the image opaque
*/
public boolean isOpaque() {
checkRecycled();
return nativeIsOpaque(mNativePtr);
}
/**
* Free the native object associated with this image.
* It must be called when the image will not be used.
* The image can't be used after this method is called.
*/
public synchronized void recycle() {
if (mNativePtr != 0) {
nativeRecycle(mNativePtr);
mNativePtr = 0;
imageList.remove(this);
}
}
/**
* Returns true if this image has been recycled.
*/
public boolean isRecycled() {
return mNativePtr == 0;
}
}
@@ -0,0 +1,329 @@
/*
* Copyright 2016 Hippo Seven
*
* 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.
*/
package com.hippo.image;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Animatable;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.FileDescriptor;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* A image with {@link Image} for data and {@link Bitmap} for render.
*/
public final class ImageBitmap implements Animatable, Runnable {
private static final Handler HANDLER = new Handler(Looper.getMainLooper());
@NonNull
private final Bitmap mBitmap;
private final int mFormat;
private final boolean mIsOpaque;
private final Set<WeakReference<Callback>> mCallbackSet = new LinkedHashSet<>();
@Nullable
private Image mImage;
private int mReferences;
private int mAnimationReferences;
private boolean mRunning;
private ImageBitmap(@NonNull Image image) throws OutOfMemoryError {
int width = image.getWidth();
int height = image.getHeight();
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
image.render(0, 0, mBitmap, 0, 0, width, height, false, 0);
mFormat = image.getFormat();
mIsOpaque = image.isOpaque();
if (mFormat == 1) {
// For animated image, save image object
mImage = image;
} else {
// Free the image
image.recycle();
}
}
private ImageBitmap(@NonNull Bitmap bitmap) {
mFormat = Image.FORMAT_NORMAL;
mBitmap = bitmap;
mIsOpaque = !bitmap.hasAlpha();
}
/**
* Decode {@code InputStream}, then create image.
*/
@Nullable
public static ImageBitmap decode(@NonNull FileDescriptor fd) {
Image image = Image.decode(fd, false);
if (image != null) {
return create(image);
} else {
return null;
}
}
/**
* Create {@code ImageBitmap} from {@code Image}.
* It is not recommended. Use {@link #decode(InputStream)} if you can.
*
* @param image the image should not be used before and
* it must not be recycled. And the image should
* not be used directly anymore.
*/
@Nullable
public static ImageBitmap create(@NonNull Image image) {
if (!image.isRecycled()) {
try {
return new ImageBitmap(image);
} catch (OutOfMemoryError e) {
image.recycle();
return null;
}
} else {
return null;
}
}
/**
* Create {@code ImageBitmap} from {@code Bitmap}.
*
* @param bitmap the bitmap should not be recycled
*/
@Nullable
public static ImageBitmap create(@NonNull Bitmap bitmap) {
if (!bitmap.isRecycled()) {
return new ImageBitmap(bitmap);
} else {
return null;
}
}
/**
* Obtain the image bitmap
*
* @return false for the image is recycled and obtain failed
*/
public synchronized boolean obtain() {
if (mBitmap.isRecycled()) {
return false;
} else {
++mReferences;
return true;
}
}
/**
* Release the image bitmap
*/
public synchronized void release() {
--mReferences;
if (mReferences <= 0 && !mBitmap.isRecycled()) {
mBitmap.recycle();
if (mImage != null) {
mImage.recycle();
}
}
}
/**
* Add a callback for invalidating
*/
public void addCallback(@NonNull Callback callback) {
final Iterator<WeakReference<Callback>> iterator = mCallbackSet.iterator();
Callback c;
while (iterator.hasNext()) {
c = iterator.next().get();
if (c == null) {
// Remove from the set if the reference has been cleared or
// it can't be used.
iterator.remove();
} else if (c == callback) {
return;
}
}
mCallbackSet.add(new WeakReference<>(callback));
}
/**
* Remove a callback
*/
public void removeCallback(@NonNull Callback callback) {
final Iterator<WeakReference<Callback>> iterator = mCallbackSet.iterator();
Callback c;
while (iterator.hasNext()) {
c = iterator.next().get();
if (c == null) {
// Remove from the set if the reference has been cleared or
// it can't be used.
iterator.remove();
} else if (c == callback) {
iterator.remove();
return;
}
}
}
/**
* Return the format of the image
*/
public int getFormat() {
return mFormat;
}
/**
* Return image width
*/
public int getWidth() {
return mBitmap.getWidth();
}
/**
* Return image height
*/
public int getHeight() {
return mBitmap.getHeight();
}
/**
* Return image is opaque
*/
public boolean isOpaque() {
return mIsOpaque;
}
/**
* Return image is animated
*/
public boolean isAnimated() {
return mImage != null;
}
/**
* Draw image to canvas
*/
public void draw(Canvas canvas, float left, float top, @Nullable Paint paint) {
if (!mBitmap.isRecycled()) {
canvas.drawBitmap(mBitmap, left, top, paint);
}
}
/**
* Draw image to canvas
*/
public void draw(Canvas canvas, @Nullable Rect src, @NonNull Rect dst, @Nullable Paint paint) {
if (!mBitmap.isRecycled()) {
canvas.drawBitmap(mBitmap, src, dst, paint);
}
}
/**
* Draw image to canvas
*/
public void draw(Canvas canvas, @Nullable Rect src, @NonNull RectF dst, @Nullable Paint paint) {
if (!mBitmap.isRecycled()) {
canvas.drawBitmap(mBitmap, src, dst, paint);
}
}
/**
* {@code start()} and {@code stop()} is a pair
*/
@Override
public void start() {
mAnimationReferences++;
if (mBitmap.isRecycled() || mImage == null || mRunning) {
return;
}
mRunning = true;
HANDLER.postDelayed(this, Math.max(0, mImage.getDelay()));
}
/**
* {@code start()} and {@code stop()} is a pair
*/
@Override
public void stop() {
mAnimationReferences--;
if (mAnimationReferences <= 0) {
mRunning = false;
HANDLER.removeCallbacks(this);
}
}
@Override
public boolean isRunning() {
return mRunning;
}
private boolean notifyUpdate() {
boolean hasCallback = false;
final Iterator<WeakReference<Callback>> iterator = mCallbackSet.iterator();
Callback callback;
while (iterator.hasNext()) {
callback = iterator.next().get();
if (callback != null) {
// Render bitmap int the first time
if (!hasCallback) {
hasCallback = true;
mImage.render(0, 0, mBitmap, 0, 0, mImage.getWidth(), mImage.getHeight(), false, 0);
}
callback.invalidateImage(this);
} else {
// Remove from the set if the reference has been cleared or
// it can't be used.
iterator.remove();
}
}
return hasCallback;
}
@Override
public void run() {
// Check recycled
if (mBitmap.isRecycled() || mImage == null) {
mRunning = false;
return;
}
mImage.advance();
if (notifyUpdate()) {
if (mRunning) {
HANDLER.postDelayed(this, Math.max(0, mImage.getDelay()));
}
} else {
mRunning = false;
}
}
public interface Callback {
void invalidateImage(ImageBitmap who);
}
}
@@ -0,0 +1,100 @@
/*
* Copyright 2016 Hippo Seven
*
* 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.
*/
package com.hippo.image;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
/**
* A drawable to draw {@link ImageBitmap}
*/
public class ImageDrawable extends Drawable implements Animatable, ImageBitmap.Callback {
private final ImageBitmap mImageBitmap;
private final Paint mPaint;
public ImageDrawable(ImageBitmap imageBitmap) throws Exception {
if (!imageBitmap.obtain()) {
throw new Exception();
}
mImageBitmap = imageBitmap;
mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
// Add callback
imageBitmap.addCallback(this);
}
public void recycle() {
mImageBitmap.removeCallback(this);
mImageBitmap.release();
}
@Override
public void draw(Canvas canvas) {
mImageBitmap.draw(canvas, null, getBounds(), mPaint);
}
@Override
public void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
mPaint.setColorFilter(colorFilter);
}
@Override
public int getOpacity() {
return mImageBitmap.isOpaque() ? PixelFormat.OPAQUE : PixelFormat.UNKNOWN;
}
@Override
public int getIntrinsicWidth() {
return mImageBitmap.getWidth();
}
@Override
public int getIntrinsicHeight() {
return mImageBitmap.getHeight();
}
@Override
public void start() {
mImageBitmap.start();
}
@Override
public void stop() {
mImageBitmap.stop();
}
@Override
public boolean isRunning() {
return mImageBitmap.isRunning();
}
@Override
public void invalidateImage(ImageBitmap who) {
invalidateSelf();
}
}