/* * 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 com.hippo.Native; 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 imageList = new ArrayList<>(); private static boolean needEvictAll = false; private long mNativePtr; private boolean needRecycle = false; private Integer nativeProcessCnt = 0; private Image(long nativePtr) { if (needEvictAll) evictAll(); mNativePtr = nativePtr; imageList.add(this); } private static Image newFromAddr(long addr) { if (addr == 0) return null; return new Image(addr); } 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 newFromAddr(nativeDecode(Native.getFd(fd))); } /** * Decode image from {@code InputStream} */ @Nullable public static Image decode(Integer fd, boolean partially) { return newFromAddr(nativeDecode(fd)); } /** * Decode image from {@code InputStream} */ @Nullable public static Image decodeAddr(Long addr, boolean partially) { return newFromAddr(nativeDecodeAddr(addr)); } /** * Create a plain image from Bitmap */ @Nullable public static Image create(Bitmap bitmap) { return newFromAddr(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 long nativeDecode(int fd); private static native long nativeDecodeAddr(long addr); private static native long 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); private static native int nativeGetFormat(long nativePtr); private static native int nativeGetWidth(long nativePtr); private static native int nativeGetHeight(long nativePtr); /** * Return the format of the image */ public int getFormat() { checkRecycledAndLock(); int ret = nativeGetFormat(mNativePtr); releaseNativeLock(); return ret; } /** * Return the width of the image */ public int getWidth() { checkRecycledAndLock(); int ret = nativeGetWidth(mNativePtr); releaseNativeLock(); return ret; } /** * Return the height of the image */ public int getHeight() { checkRecycledAndLock(); int ret = nativeGetHeight(mNativePtr); releaseNativeLock(); return ret; } private synchronized void checkRecycledAndLock() { if (mNativePtr == 0) { throw new IllegalStateException("The image is recycled."); } nativeProcessCnt++; } private synchronized void releaseNativeLock() { nativeProcessCnt--; if (needRecycle) recycle(); } /** * 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) { checkRecycledAndLock(); nativeRender(mNativePtr, srcX, srcY, dst, dstX, dstY, width, height); releaseNativeLock(); } /** * 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) { checkRecycledAndLock(); nativeTexImage(mNativePtr, init, offsetX, offsetY, width, height); releaseNativeLock(); } /** * Move to next frame. Do nothing for non-animation image */ public void advance() { checkRecycledAndLock(); nativeAdvance(mNativePtr); releaseNativeLock(); } /** * Return current frame delay. 0 for non-animation image */ public int getDelay() { checkRecycledAndLock(); int delay = nativeGetDelay(mNativePtr); releaseNativeLock(); return delay <= 10 ? 100 : delay; } /** * Return is the image opaque */ public boolean isOpaque() { checkRecycledAndLock(); boolean ret = nativeIsOpaque(mNativePtr); releaseNativeLock(); return ret; } /** * 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) { if (nativeProcessCnt != 0) { needRecycle = true; return; } nativeRecycle(mNativePtr); mNativePtr = 0; imageList.remove(this); } } /** * Returns true if this image has been recycled. */ public boolean isRecycled() { return mNativePtr == 0; } }