Skip to content

Commit

Permalink
feat(android): add ImageDataUtils for parse image type
Browse files Browse the repository at this point in the history
  • Loading branch information
siguangli2018 authored and hippy-actions[bot] committed Jan 3, 2023
1 parent 911cedc commit a011744
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,13 @@ public enum ExceptionCode {
* @see com.tencent.renderer.node.RenderNode#checkHostViewReused()
*/
REUSE_VIEW_HAS_ABANDONED_NODE_ERR,

/**
* If decoding image data encounters an exception
*
* @see com.tencent.renderer.component.image.ImageDataHolder#setData(byte[])
*/
IMAGE_DATA_DECODE_ERR,
}

public ExceptionCode mCode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,22 @@

package com.tencent.renderer.component.image;

import static com.tencent.renderer.NativeRenderException.ExceptionCode.IMAGE_DATA_DECODE_ERR;

import android.graphics.ImageDecoder;
import android.os.Build.VERSION_CODES;
import androidx.annotation.RequiresApi;
import com.tencent.renderer.NativeRenderException;
import com.tencent.renderer.utils.ImageDataUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import com.tencent.mtt.hippy.utils.ContextHolder;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Movie;
import android.graphics.drawable.Drawable;
import android.util.Base64;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
Expand Down Expand Up @@ -58,6 +59,8 @@ public class ImageDataHolder implements ImageDataSupplier {
private Bitmap mBitmap;
@Nullable
private String mImageType;
@Nullable
private BitmapFactory.Options mOptions;

public ImageDataHolder(@NonNull String source) {
mSource = source;
Expand Down Expand Up @@ -203,16 +206,21 @@ public void setDrawable(Drawable drawable) {
mDrawable = drawable;
}

public void setData(byte[] data) {
if (isGif(data)) {
mGifMovie = Movie.decodeByteArray(data, 0, data.length);
mBitmap = null;
} else {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
decodeLocalFileForTarget28(ByteBuffer.wrap(data));
public void setData(@NonNull byte[] data) throws NativeRenderException {
try {
mOptions = ImageDataUtils.generateBitmapOptions(data);
if (ImageDataUtils.isGif(mOptions)) {
mGifMovie = Movie.decodeByteArray(data, 0, data.length);
mBitmap = null;
} else {
decodeImageData(data);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
decodeLocalFileForTarget28(ByteBuffer.wrap(data));
} else {
decodeImageData(data);
}
}
} catch (OutOfMemoryError | Exception e) {
throw new NativeRenderException(IMAGE_DATA_DECODE_ERR, e.getMessage());
}
}

Expand All @@ -237,34 +245,22 @@ private void decodeImageSource(@Nullable ImageDecoder.Source source)
}

@RequiresApi(api = VERSION_CODES.P)
private void decodeLocalFileForTarget28(@NonNull File file) {
try {
ImageDecoder.Source source = ImageDecoder.createSource(file);
decodeImageSource(source);
} catch (IOException e) {
e.printStackTrace();
}
private void decodeLocalFileForTarget28(@NonNull File file) throws IOException {
ImageDecoder.Source source = ImageDecoder.createSource(file);
decodeImageSource(source);
}

@RequiresApi(api = VERSION_CODES.P)
private void decodeLocalFileForTarget28(@NonNull String fileName) {
try {
ImageDecoder.Source source = ImageDecoder.createSource(
ContextHolder.getAppContext().getAssets(), fileName);
decodeImageSource(source);
} catch (IOException e) {
e.printStackTrace();
}
private void decodeLocalFileForTarget28(@NonNull String fileName) throws IOException {
ImageDecoder.Source source = ImageDecoder.createSource(
ContextHolder.getAppContext().getAssets(), fileName);
decodeImageSource(source);
}

@RequiresApi(api = VERSION_CODES.P)
private void decodeLocalFileForTarget28(@NonNull ByteBuffer buffer) {
try {
ImageDecoder.Source source = ImageDecoder.createSource(buffer);
decodeImageSource(source);
} catch (IOException e) {
e.printStackTrace();
}
private void decodeLocalFileForTarget28(@NonNull ByteBuffer buffer) throws IOException {
ImageDecoder.Source source = ImageDecoder.createSource(buffer);
decodeImageSource(source);
}

private int getSampleSize(int outWidth, int outHeight) {
Expand All @@ -286,33 +282,16 @@ private int getSampleSize(int outWidth, int outHeight) {
}

private void decodeImageData(@Nullable byte[] data) {
if (data == null || data.length <= 0) {
if (data == null || data.length <= 0 || mOptions == null) {
return;
}
try {
// When using the BitmapFactory decoding provided by the old version of Android system,
// we need to sample the image to scale in order to prevent the decoding memory
// growth caused by large images.
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, 0, data.length, options);
options.inSampleSize = getSampleSize(options.outWidth, options.outHeight);
options.inJustDecodeBounds = false;
mBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);
mGifMovie = null;
setStateFlag(FLAG_RECYCLABLE);
} catch (OutOfMemoryError | Exception e) {
e.printStackTrace();
}
}

private boolean isGif(byte[] bytes) {
if (bytes == null || bytes.length < 3) {
return false;
}
byte b0 = bytes[0];
byte b1 = bytes[1];
byte b2 = bytes[2];
return b0 == (byte) 'G' && b1 == (byte) 'I' && b2 == (byte) 'F';
// When using the BitmapFactory decoding provided by the old version of Android system,
// we need to sample the image to scale in order to prevent the decoding memory
// growth caused by large images.
mOptions.inSampleSize = getSampleSize(mOptions.outWidth, mOptions.outHeight);
mOptions.inJustDecodeBounds = false;
mBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, mOptions);
mGifMovie = null;
setStateFlag(FLAG_RECYCLABLE);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import com.tencent.mtt.hippy.dom.node.NodeProps;
import com.tencent.mtt.hippy.utils.UIThreadUtils;
import com.tencent.renderer.NativeRenderException;
import com.tencent.renderer.pool.ImageDataPool;
import com.tencent.renderer.pool.Pool;

Expand Down Expand Up @@ -49,21 +50,49 @@ public ImageDataSupplier getImageFromCache(@NonNull String source) {
return mImagePool.acquire(ImageDataHolder.generateSourceKey(source));
}

private void doCallback(@NonNull final ResourceDataHolder dataHolder,
@Nullable final ImageDataHolder imageHolder,
@NonNull final ImageRequestListener listener) {
UIThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
if (dataHolder.resultCode != ResourceDataHolder.RESOURCE_LOAD_SUCCESS_CODE) {
listener.onRequestFail(new RuntimeException(dataHolder.errorMessage));
} else if (imageHolder == null || !imageHolder.checkImageData()) {
listener.onRequestFail(new RuntimeException(""));
private void handleResourceData(@NonNull String url,
@NonNull final ResourceDataHolder dataHolder,
@NonNull final ImageRequestListener listener, int width, int height) {
Runnable callbackTask = null;
String errorMessage = null;
byte[] bytes = dataHolder.getBytes();
if (dataHolder.resultCode
== ResourceDataHolder.RESOURCE_LOAD_SUCCESS_CODE && bytes != null) {
final ImageDataHolder imageHolder = new ImageDataHolder(url, width, height);
try {
imageHolder.setData(bytes);
if (imageHolder.checkImageData()) {
callbackTask = new Runnable() {
@Override
public void run() {
listener.onRequestSuccess(imageHolder);
}
};
saveImageToCache(imageHolder);
} else {
listener.onRequestSuccess(imageHolder);
errorMessage = "Image data decoding failed!";
}
} catch (NativeRenderException e) {
e.printStackTrace();
errorMessage = e.getMessage();
}
});
} else {
errorMessage = dataHolder.errorMessage;
}
if (callbackTask == null) {
final String error = (errorMessage != null) ? errorMessage : "";
callbackTask = new Runnable() {
@Override
public void run() {
listener.onRequestFail(new RuntimeException(error));
}
};
}
if (UIThreadUtils.isOnUiThread()) {
callbackTask.run();
} else {
UIThreadUtils.runOnUiThread(callbackTask);
}
}

private void saveImageToCache(@NonNull ImageDataSupplier data) {
Expand Down Expand Up @@ -103,14 +132,16 @@ public ImageDataSupplier fetchImageSync(@NonNull String url,
return null;
}
ImageDataHolder imageHolder = new ImageDataHolder(url, width, height);
imageHolder.setData(bytes);
if (!imageHolder.checkImageData()) {
// The source decoding may fail, if bitmap and gif movie does not exist,
// return null directly.
return null;
try {
imageHolder.setData(bytes);
if (imageHolder.checkImageData()) {
saveImageToCache(imageHolder);
return imageHolder;
}
} catch (NativeRenderException e) {
e.printStackTrace();
}
saveImageToCache(imageHolder);
return imageHolder;
return null;
}

@Override
Expand All @@ -122,22 +153,7 @@ public void fetchImageAsync(@NonNull final String url,
new FetchResourceCallback() {
@Override
public void onFetchCompleted(@NonNull final ResourceDataHolder dataHolder) {
byte[] bytes = dataHolder.getBytes();
if (dataHolder.resultCode
!= ResourceDataHolder.RESOURCE_LOAD_SUCCESS_CODE || bytes == null) {
doCallback(dataHolder, null, listener);
} else {
ImageDataHolder imageHolder = new ImageDataHolder(url, width, height);
imageHolder.setData(bytes);
if (!imageHolder.checkImageData()) {
// The source decoding may fail, if bitmap and gif movie does not exist,
// callback request failed.
doCallback(dataHolder, null, listener);
} else {
saveImageToCache(imageHolder);
doCallback(dataHolder, imageHolder, listener);
}
}
handleResourceData(url, dataHolder, listener, width, height);
}

@Override
Expand All @@ -150,8 +166,4 @@ public void onFetchProgress(float total, float loaded) {
public void clear() {
mImagePool.clear();
}

public void destroyIfNeed() {
clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Map;
import java.util.concurrent.Executor;

public interface ImageLoaderAdapter {

Expand All @@ -33,7 +32,5 @@ ImageDataSupplier fetchImageSync(@NonNull String url, @Nullable Map<String, Obje
@Nullable
ImageDataSupplier getImageFromCache(@NonNull String source);

void destroyIfNeed();

void clear();
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package com.tencent.renderer.component.image;

import androidx.annotation.Nullable;

public interface ImageRequestListener {

/**
Expand All @@ -31,7 +33,7 @@ public interface ImageRequestListener {
/**
* Notify image request failed
*/
void onRequestFail(Throwable throwable);
void onRequestFail(@Nullable Throwable throwable);

/**
* Notify image request on progress
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/* Tencent is pleased to support the open source community by making Hippy available.
* Copyright (C) 2018 THL A29 Limited, a Tencent company. 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.
*/

package com.tencent.renderer.utils;

import android.graphics.BitmapFactory;
import android.text.TextUtils;
import androidx.annotation.NonNull;

public class ImageDataUtils {

public static final String IMAGE_TYPE_PNG = "image/png";
public static final String IMAGE_TYPE_JPEG = "image/jpeg";
public static final String IMAGE_TYPE_GIF = "image/gif";
public static final String IMAGE_TYPE_WEBP = "image/webp";

@NonNull
public static BitmapFactory.Options generateBitmapOptions(@NonNull byte[] data)
throws IllegalArgumentException {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, 0, data.length, options);
return options;
}

public static boolean isWebp(@NonNull BitmapFactory.Options options) {
return !TextUtils.isEmpty(options.outMimeType) && options.outMimeType.equalsIgnoreCase(
IMAGE_TYPE_WEBP);
}

public static boolean isJpeg(@NonNull BitmapFactory.Options options) {
return !TextUtils.isEmpty(options.outMimeType) && options.outMimeType.equalsIgnoreCase(
IMAGE_TYPE_JPEG);
}

public static boolean isPng(@NonNull BitmapFactory.Options options) {
return !TextUtils.isEmpty(options.outMimeType) && options.outMimeType.equalsIgnoreCase(
IMAGE_TYPE_PNG);
}

public static boolean isGif(@NonNull BitmapFactory.Options options) {
return !TextUtils.isEmpty(options.outMimeType) && options.outMimeType.equalsIgnoreCase(
IMAGE_TYPE_GIF);
}

}

0 comments on commit a011744

Please sign in to comment.