Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5fcb06e8f | ||
|
|
562d1f9a0d | ||
|
|
03f29288ad | ||
|
|
eb84320225 | ||
|
|
10d73ab639 | ||
|
|
cac8f6361a | ||
|
|
32d0758fdc | ||
|
|
c382c9c896 | ||
|
|
e772a52bf2 | ||
|
|
edda52159e | ||
|
|
00c2e683bd | ||
|
|
a88766db56 | ||
|
|
f6c1092cba | ||
|
|
a15b92333a | ||
|
|
21fc20a0f4 | ||
|
|
bf72d772b5 | ||
|
|
76d8575398 | ||
|
|
029dc7ac61 | ||
|
|
0b6f8d1d6f | ||
|
|
0b283bb333 | ||
|
|
c74ee2b5cb | ||
|
|
8263dce3ca | ||
|
|
a37e5e1ab3 | ||
|
|
0baad6c042 | ||
|
|
a2da9ffea6 | ||
|
|
4f3668dae4 | ||
|
|
2598b68b97 | ||
|
|
7d065fb5c5 | ||
|
|
c4cc089eca | ||
|
|
85c377713d | ||
|
|
3e7a1d9d35 | ||
|
|
b0e9d8ec66 | ||
|
|
c25163b3c4 | ||
|
|
12d5aa9e08 | ||
|
|
9fe1743dd9 | ||
|
|
e178880eb4 | ||
|
|
0abab346ab | ||
|
|
63d355ad95 | ||
|
|
17ab6c7532 | ||
|
|
8d82fc39f6 | ||
|
|
d396cae207 | ||
|
|
ec96ca2d7c | ||
|
|
fa2279e480 | ||
|
|
de5b77cc96 | ||
|
|
41a54b76bf | ||
|
|
a09940eb86 | ||
|
|
159b8c13b0 | ||
|
|
498d51ec22 | ||
|
|
24e50da7c9 | ||
|
|
fcc595d9a3 | ||
|
|
d2aecb5eb5 | ||
|
|
e2cfa30052 |
No files matched your search
@@ -57,7 +57,7 @@ body:
|
||||
id: ci
|
||||
attributes:
|
||||
label: EhViewer CI 版本 / EhViewer CI Version
|
||||
description: 直接将对应 CI 下载页面网址填入即可 / Simply drop CI download page url here
|
||||
description: 直接将对应 GitHub Action CI 下载页面网址填入即可 / Simply drop GitHub Action CI download page url here
|
||||
placeholder: https://github.com/Ehviewer-Overhauled/Ehviewer/actions/runs/XXXXXXXXXX
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 所有其他问题 / All other questions
|
||||
url: https://github.com/Ehviewer-Overhauled/Ehviewer/discussions
|
||||
|
||||
@@ -56,7 +56,7 @@ body:
|
||||
id: ci
|
||||
attributes:
|
||||
label: EhViewer CI 版本 / EhViewer CI Version
|
||||
description: 直接将对应 CI 下载页面网址填入即可 / Simply drop CI download page url here
|
||||
description: 直接将对应 GitHub Action CI 下载页面网址填入即可 / Simply drop GitHub Action CI download page url here
|
||||
placeholder: https://github.com/Ehviewer-Overhauled/Ehviewer/actions/runs/XXXXXXXXXX
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
name: CI
|
||||
|
||||
on: [push]
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
tags-ignore:
|
||||
- "*"
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
@@ -9,6 +14,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Setup Java 11
|
||||
uses: actions/setup-java@v3
|
||||
@@ -16,18 +23,14 @@ jobs:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
|
||||
- name: Setup Android SDK Tools
|
||||
uses: android-actions/setup-android@v2
|
||||
|
||||
- name: Install NDK
|
||||
run: |
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
|
||||
- name: Gradle Build
|
||||
uses: gradle/gradle-build-action@v2
|
||||
- run: |
|
||||
git submodule update --init --recursive
|
||||
gradle assembleRelease
|
||||
with:
|
||||
arguments: assembleRelease
|
||||
|
||||
- name: Update ARM64
|
||||
uses: actions/upload-artifact@v3
|
||||
|
||||
@@ -9,6 +9,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Setup Java 11
|
||||
uses: actions/setup-java@v3
|
||||
@@ -16,18 +18,14 @@ jobs:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
|
||||
- name: Setup Android SDK Tools
|
||||
uses: android-actions/setup-android@v2
|
||||
|
||||
- name: Install NDK
|
||||
run: |
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
|
||||
- name: Gradle Build
|
||||
uses: gradle/gradle-build-action@v2
|
||||
- run: |
|
||||
git submodule update --init --recursive
|
||||
gradle assembleRelease
|
||||
with:
|
||||
arguments: assembleRelease
|
||||
|
||||
- name: Update ARM64
|
||||
uses: actions/upload-artifact@v3
|
||||
|
||||
@@ -12,6 +12,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Setup Java 11
|
||||
uses: actions/setup-java@v3
|
||||
@@ -19,26 +21,31 @@ jobs:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
|
||||
- name: Setup Android SDK Tools
|
||||
uses: android-actions/setup-android@v2
|
||||
|
||||
- name: Install NDK
|
||||
run: |
|
||||
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
|
||||
- name: Gradle Build
|
||||
uses: gradle/gradle-build-action@v2
|
||||
- run: |
|
||||
git submodule update --init --recursive
|
||||
gradle assembleRelease
|
||||
with:
|
||||
arguments: assembleRelease
|
||||
|
||||
- name: Rename Apks
|
||||
run: |
|
||||
cp app/build/outputs/apk/release/app-universal-release.apk app/build/outputs/apk/release/EhViewer-universal-A11-.apk
|
||||
cp app/build/outputs/apk/release/app-arm64-v8a-release.apk app/build/outputs/apk/release/EhViewer-arm64-v8a-A11-.apk
|
||||
cp app/build/outputs/apk/release/app-x86_64-release.apk app/build/outputs/apk/release/EhViewer-x86_64-A11-.apk
|
||||
cp app/build/outputs/apk/release/app-x86-release.apk app/build/outputs/apk/release/EhViewer-x86-A11-.apk
|
||||
cp app/build/outputs/apk/release/app-armeabi-v7a-release.apk app/build/outputs/apk/release/EhViewer-armeabi-v7a-A11-.apk
|
||||
|
||||
- name: Releases
|
||||
uses: softprops/action-gh-release@v0.1.14
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
body: Bump Version
|
||||
discussion_category_name: Announcements
|
||||
files: |
|
||||
app/build/outputs/apk/release/app-arm64-v8a-release.apk
|
||||
app/build/outputs/apk/release/app-x86_64-release.apk
|
||||
app/build/outputs/apk/release/app-x86-release.apk
|
||||
app/build/outputs/apk/release/app-armeabi-v7a-release.apk
|
||||
app/build/outputs/apk/release/EhViewer-universal-A11-.apk
|
||||
app/build/outputs/apk/release/EhViewer-arm64-v8a-A11-.apk
|
||||
app/build/outputs/apk/release/EhViewer-x86_64-A11-.apk
|
||||
app/build/outputs/apk/release/EhViewer-x86-A11-.apk
|
||||
app/build/outputs/apk/release/EhViewer-armeabi-v7a-A11-.apk
|
||||
@@ -1,3 +1,12 @@
|
||||
[submodule "native/src/main/jni/image/libjpeg-turbo/libjpeg-turbo"]
|
||||
path = native/src/main/jni/image/libjpeg-turbo/libjpeg-turbo
|
||||
url = https://github.com/Ehviewer-Overhauled/libjpeg-turbo
|
||||
[submodule "native/src/main/jni/image/giflib/giflib"]
|
||||
path = native/src/main/jni/image/giflib/giflib
|
||||
url = https://github.com/Ehviewer-Overhauled/giflib
|
||||
[submodule "native/src/main/jni/image/libpng/libpng"]
|
||||
path = native/src/main/jni/image/libpng/libpng
|
||||
url = https://github.com/Ehviewer-Overhauled/libpng
|
||||
[submodule "native/src/main/jni/libarchive"]
|
||||
path = native/src/main/jni/libarchive
|
||||
url = https://github.com/Ehviewer-Overhauled/libarchive
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
<p align="right">
|
||||
<strong>English<strong>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-cn.md">
|
||||
简体中文
|
||||
</a>
|
||||
<span> | <span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-tw.md">
|
||||
正體中文
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<h1 align="center">
|
||||
<img src="art/launcher_icon-web.png" width="200">
|
||||
<img src="art/launcher_icon-web.svg" width="200">
|
||||
<br>EhViewer<br>
|
||||
</h1>
|
||||
|
||||
@@ -7,7 +19,7 @@
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml">
|
||||
<img src="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml/badge.svg" alt="Github Actions">
|
||||
</a>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/master/LICENSE">
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/Ehviewer-Overhauled/Ehviewer" alt="LICENSE">
|
||||
</a>
|
||||
<a href="https://www.codefactor.io/repository/github/Ehviewer-Overhauled/ehviewer">
|
||||
@@ -21,22 +33,42 @@
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
<h3>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/tree/1.7.x.x#description">
|
||||
Description
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/tree/1.7.x.x#download">
|
||||
Download
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/tree/1.7.x.x#screenshot">
|
||||
Screenshot
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/tree/1.7.x.x#thanks">
|
||||
Thanks
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/tree/1.7.x.x#license">
|
||||
License
|
||||
</a>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
|
||||
# Description
|
||||
|
||||
致力于轻量化与良好性能表现的 EhViewer 分支
|
||||
|
||||
已针对性适配 Material Design 3 及其动态取色系统
|
||||
|
||||
|
||||
EhViewer fork dedicated to lightweight and high-performance
|
||||
|
||||
with Material Design 3 and Dynamic Color Support
|
||||
|
||||
# Download
|
||||
|
||||
[Github 下载](https://github.com/Ehviewer-Overhauled/Ehviewer/releases)
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases"><img alt="Get it on Github" src="art/get-it-on-github.svg" width="200px"/></a>
|
||||
|
||||
[CI 版本下载(需登录)](https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml)
|
||||
App is divided into two versions: 1.7 and 1.8 . Version 1.8 support Android 12 and above, also the develop mainline. Version 1.7 support Android 11 and below, drop down some spicetify functions for Android 12 to make app useable on lower Android System
|
||||
|
||||
# Screenshot
|
||||
|
||||
@@ -45,12 +77,11 @@ with Material Design 3 and Dynamic Color Support
|
||||
|
||||
# Thanks
|
||||
|
||||
本项目受到了诸多开源项目的帮助
|
||||
|
||||
Here is the libraries
|
||||
|
||||
- [AOSP](http://source.android.com/)
|
||||
- [android-advancedrecyclerview](https://github.com/h6ah4i/android-advancedrecyclerview)
|
||||
- [AOSP&AndroidX](http://source.android.com/)
|
||||
- [FullDraggableDrawer](https://github.com/PureWriter/FullDraggableDrawer)
|
||||
- [GnuTLS](https://gnutls.org/)
|
||||
- [material-components-android](https://github.com/material-components/material-components-android)
|
||||
- [material-design-icons](https://github.com/google/material-design-icons)
|
||||
- [Apache Commons Lang](https://commons.apache.org/proper/commons-lang/)
|
||||
@@ -62,7 +93,6 @@ Here is the libraries
|
||||
- [libpng](http://www.libpng.org/pub/png/libpng.html)
|
||||
- [okhttp](https://github.com/square/okhttp)
|
||||
- [roaster](https://github.com/forge/roaster)
|
||||
- [TapTargetView](https://github.com/KeepSafe/TapTargetView)
|
||||
- [libarchive](http://www.libarchive.org/)
|
||||
- [XZ Utils](https://tukaani.org/xz/)
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ android {
|
||||
enable = true
|
||||
reset()
|
||||
include 'arm64-v8a', 'x86_64', 'armeabi-v7a', 'x86'
|
||||
universalApk = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,10 +33,10 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "moe.tarsin.ehviewer"
|
||||
minSdkVersion 31
|
||||
minSdkVersion 26
|
||||
targetSdkVersion 33
|
||||
versionCode 180000
|
||||
versionName "1.8.0.0"
|
||||
versionCode 114536
|
||||
versionName "1.7.30.0"
|
||||
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
|
||||
}
|
||||
|
||||
@@ -92,13 +93,13 @@ tasks.withType(JavaCompile) {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
def activity_version = "1.6.0-alpha05"
|
||||
def activity_version = "1.6.0-beta01"
|
||||
implementation "androidx.activity:activity:$activity_version"
|
||||
implementation "androidx.activity:activity-ktx:$activity_version"
|
||||
|
||||
implementation "androidx.annotation:annotation:1.4.0"
|
||||
implementation "androidx.annotation:annotation-experimental:1.2.0"
|
||||
implementation "androidx.appcompat:appcompat:1.6.0-alpha05"
|
||||
implementation "androidx.appcompat:appcompat:1.6.0-beta01"
|
||||
|
||||
def arch_core_version = "2.1.0"
|
||||
implementation "androidx.arch.core:core-common:$arch_core_version"
|
||||
@@ -107,14 +108,14 @@ dependencies {
|
||||
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha04"
|
||||
implementation "androidx.browser:browser:1.4.0"
|
||||
|
||||
def collection_version = "1.3.0-alpha01"
|
||||
def collection_version = "1.3.0-alpha02"
|
||||
implementation "androidx.collection:collection:$collection_version"
|
||||
implementation "androidx.collection:collection-ktx:$collection_version"
|
||||
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha03"
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
|
||||
|
||||
def core_version = "1.9.0-alpha05"
|
||||
def core_version = "1.9.0-beta01"
|
||||
implementation "androidx.core:core:$core_version"
|
||||
implementation "androidx.core:core-ktx:$core_version"
|
||||
|
||||
@@ -122,12 +123,12 @@ dependencies {
|
||||
implementation "androidx.documentfile:documentfile:1.1.0-alpha01"
|
||||
implementation "androidx.dynamicanimation:dynamicanimation:1.1.0-alpha03"
|
||||
|
||||
def fragment_version = "1.5.0"
|
||||
def fragment_version = "1.5.2"
|
||||
implementation "androidx.fragment:fragment:$fragment_version"
|
||||
implementation "androidx.fragment:fragment-ktx:$fragment_version"
|
||||
|
||||
def lifecycle_version = "2.6.0-alpha01"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-core-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
|
||||
@@ -136,7 +137,7 @@ dependencies {
|
||||
|
||||
implementation "androidx.loader:loader:1.1.0"
|
||||
implementation 'androidx.preference:preference:1.2.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.0-beta01'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.0-beta02'
|
||||
implementation 'androidx.startup:startup-runtime:1.2.0-alpha01'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
|
||||
implementation "androidx.transition:transition-ktx:1.4.1"
|
||||
@@ -144,19 +145,22 @@ dependencies {
|
||||
implementation "androidx.vectordrawable:vectordrawable-animated:1.1.0"
|
||||
implementation 'androidx.viewpager:viewpager:1.1.0-alpha01'
|
||||
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
|
||||
implementation 'androidx.webkit:webkit:1.5.0-beta01'
|
||||
implementation 'androidx.webkit:webkit:1.5.0-rc01'
|
||||
|
||||
implementation 'com.drakeet.drawer:drawer:1.0.3'
|
||||
implementation 'com.google.android.material:material:1.7.0-alpha03'
|
||||
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.13.3'
|
||||
implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.10'
|
||||
implementation 'com.google.android.material:material:1.7.0-beta01'
|
||||
|
||||
implementation 'com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.10'
|
||||
implementation 'com.squareup.okhttp3:okhttp'
|
||||
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps'
|
||||
|
||||
implementation 'dev.rikka.rikkax.core:core-ktx:1.4.1'
|
||||
implementation 'dev.rikka.rikkax.insets:insets:1.3.0'
|
||||
implementation 'dev.rikka.rikkax.layoutinflater:layoutinflater:1.2.0'
|
||||
implementation 'dev.rikka.rikkax.preference:simplemenu-preference:1.0.3'
|
||||
implementation 'dev.rikka.rikkax.material:material-preference:2.0.0'
|
||||
implementation 'org.greenrobot:greendao:3.3.0'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
|
||||
implementation 'org.jsoup:jsoup:1.15.2'
|
||||
implementation projects.native
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package com.hippo.ehviewer;
|
||||
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
@@ -25,9 +23,7 @@ import com.hippo.conaco.ValueHelper;
|
||||
import com.hippo.image.ImageBitmap;
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class ImageBitmapHelper implements ValueHelper<ImageBitmap> {
|
||||
|
||||
@@ -38,8 +34,7 @@ public class ImageBitmapHelper implements ValueHelper<ImageBitmap> {
|
||||
public ImageBitmap decode(@NonNull InputStreamPipe isPipe) {
|
||||
try {
|
||||
isPipe.obtain();
|
||||
FileInputStream is = (FileInputStream) isPipe.open();
|
||||
return ImageBitmap.decode(is.getFD());
|
||||
return ImageBitmap.decode(isPipe.open());
|
||||
} catch (OutOfMemoryError | IOException e) {
|
||||
return null;
|
||||
} finally {
|
||||
|
||||
@@ -100,6 +100,11 @@ public class Settings {
|
||||
private static final int DEFAULT_VERSION_CODE = 0;
|
||||
private static final String KEY_DISPLAY_NAME = "display_name";
|
||||
private static final String DEFAULT_DISPLAY_NAME = null;
|
||||
public static final String KEY_LIST_THUMB_SIZE = "list_tile_size";
|
||||
private static final int DEFAULT_LIST_THUMB_SIZE = 40;
|
||||
private static int LIST_THUMB_SIZE = 40;
|
||||
|
||||
public static boolean LIST_THUMB_SIZE_INITED = false;
|
||||
private static final String KEY_AVATAR = "avatar";
|
||||
private static final String DEFAULT_AVATAR = null;
|
||||
private static final String KEY_SHOW_WARNING = "show_warning";
|
||||
@@ -1168,4 +1173,14 @@ public class Settings {
|
||||
public static void putPasswdToArchivePasswds(String value) {
|
||||
putStringToStringSet(KEY_ARCHIVE_PASSWDS, value);
|
||||
}
|
||||
|
||||
public static int getListThumbSize() {
|
||||
if (LIST_THUMB_SIZE_INITED) {
|
||||
return LIST_THUMB_SIZE;
|
||||
}
|
||||
int size = 3 * getInt(KEY_LIST_THUMB_SIZE, DEFAULT_LIST_THUMB_SIZE);
|
||||
LIST_THUMB_SIZE = size;
|
||||
LIST_THUMB_SIZE_INITED = true;
|
||||
return size;
|
||||
}
|
||||
}
|
||||
@@ -34,9 +34,6 @@ import com.hippo.unifile.UniFile;
|
||||
import com.hippo.yorozuya.thread.PVLock;
|
||||
import com.hippo.yorozuya.thread.PriorityThread;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -53,7 +50,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
private final UriArchiveAccessor archiveAccessor;
|
||||
private final Stack<Integer> requests = new Stack<>();
|
||||
private final AtomicInteger extractingIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX);
|
||||
private final LinkedHashMap<Integer, Long> streams = new LinkedHashMap<>();
|
||||
private final LinkedHashMap<Integer, InputStream> streams = new LinkedHashMap<>();
|
||||
private final AtomicInteger decodingIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX);
|
||||
private Thread archiveThread;
|
||||
private Thread decodeThread;
|
||||
@@ -253,18 +250,18 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
continue;
|
||||
}
|
||||
|
||||
Pipe pipe = new Pipe();
|
||||
|
||||
synchronized (streams) {
|
||||
if (streams.get(index) != null) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
long addr = archiveAccessor.extracttoOutputStream(index);
|
||||
|
||||
synchronized (streams) {
|
||||
streams.put(index, addr);
|
||||
streams.put(index, pipe.getInputStream());
|
||||
streams.notify();
|
||||
}
|
||||
|
||||
archiveAccessor.extracttoOutputStream(index, pipe.getOutputFd());
|
||||
pipe.closeOutputFd();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -274,7 +271,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
public void run() {
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
int index;
|
||||
Long addr;
|
||||
InputStream stream;
|
||||
synchronized (streams) {
|
||||
if (streams.isEmpty()) {
|
||||
try {
|
||||
@@ -286,16 +283,16 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
continue;
|
||||
}
|
||||
|
||||
Iterator<Map.Entry<Integer, Long>> iterator = streams.entrySet().iterator();
|
||||
Map.Entry<Integer, Long> entry = iterator.next();
|
||||
Iterator<Map.Entry<Integer, InputStream>> iterator = streams.entrySet().iterator();
|
||||
Map.Entry<Integer, InputStream> entry = iterator.next();
|
||||
iterator.remove();
|
||||
index = entry.getKey();
|
||||
addr = entry.getValue();
|
||||
stream = entry.getValue();
|
||||
decodingIndex.lazySet(index);
|
||||
}
|
||||
|
||||
try {
|
||||
Image image = Image.decodeAddr(addr, true);
|
||||
Image image = Image.decode(stream, true);
|
||||
if (image != null) {
|
||||
notifyPageSucceed(index, image);
|
||||
} else {
|
||||
|
||||
@@ -34,7 +34,6 @@ import com.hippo.yorozuya.IOUtils;
|
||||
import com.hippo.yorozuya.StringUtils;
|
||||
import com.hippo.yorozuya.thread.PriorityThread;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
@@ -259,7 +258,7 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
|
||||
InputStream is = null;
|
||||
try {
|
||||
is = files[index].openInputStream();
|
||||
Image image = Image.decode(((FileInputStream)is).getFD(), true);
|
||||
Image image = Image.decode(is, true);
|
||||
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
|
||||
if (image != null) {
|
||||
notifyPageSucceed(index, image);
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2019 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.ehviewer.gallery;
|
||||
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
class Pipe {
|
||||
private ParcelFileDescriptor[] fd;
|
||||
{
|
||||
try {
|
||||
fd = ParcelFileDescriptor.createPipe();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private final InputStream inputStream = new FileInputStream(fd[0].getFileDescriptor());
|
||||
|
||||
InputStream getInputStream() {
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
int getOutputFd() {
|
||||
return fd[1].getFd();
|
||||
}
|
||||
|
||||
void closeOutputFd() {
|
||||
try {
|
||||
fd[1].close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@ class VersionPreference @JvmOverloads constructor(
|
||||
val info = NativeLibInfo().addLib(Native.getlibarchiveVersion())
|
||||
.addLib("lzma(xz-utils)", Native.getliblzmaVersion())
|
||||
.addLib("zlib", Native.getzlibVersion())
|
||||
.addLib("libjpeg-turbo", Native.getlibjpeg_turboVersion())
|
||||
.addLib(Native.getnettleVersion())
|
||||
builder.setMessage(info.getMessage())
|
||||
}
|
||||
|
||||
@@ -66,7 +66,6 @@ import com.hippo.yorozuya.thread.PriorityThread;
|
||||
import com.hippo.yorozuya.thread.PriorityThreadFactory;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
@@ -1097,6 +1096,33 @@ public final class SpiderQueen implements Runnable {
|
||||
void onGetImageFailure(int index, String error);
|
||||
}
|
||||
|
||||
private static class AutoCloseInputStream extends InputStream {
|
||||
|
||||
private final InputStreamPipe mPipe;
|
||||
private final InputStream mIs;
|
||||
|
||||
public AutoCloseInputStream(InputStreamPipe pipe, InputStream is) {
|
||||
mPipe = pipe;
|
||||
mIs = is;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return mIs.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@NonNull byte[] buffer, int byteOffset, int byteCount) throws IOException {
|
||||
return mIs.read(buffer, byteOffset, byteCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
mPipe.close();
|
||||
mPipe.release();
|
||||
}
|
||||
}
|
||||
|
||||
private class SpiderWorker implements Runnable {
|
||||
|
||||
private final long mGid;
|
||||
@@ -1667,7 +1693,7 @@ public final class SpiderQueen implements Runnable {
|
||||
|
||||
pipe.obtain();
|
||||
try {
|
||||
is = pipe.open();
|
||||
is = new AutoCloseInputStream(pipe, pipe.open());
|
||||
} catch (IOException e) {
|
||||
// Can't open pipe
|
||||
error = GetText.getString(R.string.error_reading_failed);
|
||||
@@ -1677,18 +1703,7 @@ public final class SpiderQueen implements Runnable {
|
||||
}
|
||||
|
||||
if (is != null) {
|
||||
try {
|
||||
image = Image.decode(((FileInputStream)is).getFD(), true);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
image = null;
|
||||
} finally {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
image = Image.decode(is, true);
|
||||
if (image == null) {
|
||||
error = GetText.getString(R.string.error_decoding_failed);
|
||||
}
|
||||
|
||||
@@ -394,17 +394,22 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
|
||||
}
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_SCROLL:
|
||||
if (event.getAxisValue(MotionEvent.AXIS_VSCROLL) < 0.0f) {
|
||||
float scroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL) * 300;
|
||||
if (scroll < 0.0f) {
|
||||
if (mLayoutMode == GalleryView.LAYOUT_RIGHT_TO_LEFT) {
|
||||
mGalleryView.pageLeft();
|
||||
} else {
|
||||
} else if (mLayoutMode == GalleryView.LAYOUT_LEFT_TO_RIGHT){
|
||||
mGalleryView.pageRight();
|
||||
} else if (mLayoutMode == GalleryView.LAYOUT_TOP_TO_BOTTOM) {
|
||||
mGalleryView.onScroll(0, -scroll, 0, -scroll, 0, -scroll);
|
||||
}
|
||||
} else {
|
||||
if (mLayoutMode == GalleryView.LAYOUT_RIGHT_TO_LEFT) {
|
||||
mGalleryView.pageRight();
|
||||
} else {
|
||||
} else if (mLayoutMode == GalleryView.LAYOUT_LEFT_TO_RIGHT) {
|
||||
mGalleryView.pageLeft();
|
||||
} else if (mLayoutMode == GalleryView.LAYOUT_TOP_TO_BOTTOM) {
|
||||
mGalleryView.onScroll(0, -scroll, 0, -scroll, 0, -scroll);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,7 +307,6 @@ public final class MainActivity extends StageActivity
|
||||
|
||||
mStageLayout = (EhStageLayout) ViewUtils.$$(this, R.id.fragment_container);
|
||||
mDrawerLayout = (DrawerLayout) ViewUtils.$$(this, R.id.draw_view);
|
||||
mDrawerLayout.setStatusBarBackgroundColor(0);
|
||||
mNavView = (NavigationView) ViewUtils.$$(this, R.id.nav_view);
|
||||
mRightDrawer = (DrawerView) ViewUtils.$$(this, R.id.right_drawer);
|
||||
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* 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.ehviewer.ui;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.ui.fragment.SettingsFragment;
|
||||
import com.hippo.ehviewer.ui.scene.BaseScene;
|
||||
|
||||
public final class SettingsActivity extends EhActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_preference);
|
||||
setSupportActionBar(findViewById(R.id.toolbar));
|
||||
ActionBar bar = getSupportActionBar();
|
||||
if (bar != null) {
|
||||
bar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
if (savedInstanceState == null) {
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_MATCH_ACTIVITY_OPEN)
|
||||
.replace(R.id.fragment, new SettingsFragment())
|
||||
.commitAllowingStateLoss();
|
||||
}
|
||||
}
|
||||
|
||||
public void showTip(@StringRes int id, int length) {
|
||||
showTip(getString(id), length);
|
||||
}
|
||||
|
||||
public void showTip(CharSequence message, int length) {
|
||||
Snackbar.make(findViewById(R.id.snackbar), message,
|
||||
length == BaseScene.LENGTH_LONG ? Snackbar.LENGTH_LONG : Snackbar.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.ehviewer.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.ui.fragment.SettingsFragment
|
||||
import com.hippo.ehviewer.ui.scene.BaseScene
|
||||
|
||||
class SettingsActivity : EhActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_preference)
|
||||
setSupportActionBar(findViewById(R.id.toolbar))
|
||||
val bar = supportActionBar
|
||||
bar?.setDisplayHomeAsUpEnabled(true)
|
||||
WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars = (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_YES) <= 0
|
||||
if (savedInstanceState == null) {
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_MATCH_ACTIVITY_OPEN)
|
||||
.replace(R.id.fragment, SettingsFragment())
|
||||
.commitAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
|
||||
fun showTip(@StringRes id: Int, length: Int) {
|
||||
showTip(getString(id), length)
|
||||
}
|
||||
|
||||
fun showTip(message: CharSequence?, length: Int) {
|
||||
Snackbar.make(
|
||||
findViewById(R.id.snackbar), message!!,
|
||||
if (length == BaseScene.LENGTH_LONG) Snackbar.LENGTH_LONG else Snackbar.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == android.R.id.home) {
|
||||
onBackPressed()
|
||||
return true
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ public class EhFragment extends BasePreferenceFragment {
|
||||
Preference blackDarkTheme = findPreference(Settings.KEY_BLACK_DARK_THEME);
|
||||
Preference gallerySite = findPreference(Settings.KEY_GALLERY_SITE);
|
||||
Preference listMode = findPreference(Settings.KEY_LIST_MODE);
|
||||
Preference listThumbSize = findPreference(Settings.KEY_LIST_THUMB_SIZE);
|
||||
Preference detailSize = findPreference(Settings.KEY_DETAIL_SIZE);
|
||||
Preference thumbSize = findPreference(Settings.KEY_THUMB_SIZE);
|
||||
Preference showTagTranslations = findPreference(Settings.KEY_SHOW_TAG_TRANSLATIONS);
|
||||
@@ -46,6 +47,7 @@ public class EhFragment extends BasePreferenceFragment {
|
||||
theme.setOnPreferenceChangeListener(this);
|
||||
gallerySite.setOnPreferenceChangeListener(this);
|
||||
listMode.setOnPreferenceChangeListener(this);
|
||||
listThumbSize.setOnPreferenceChangeListener(this);
|
||||
detailSize.setOnPreferenceChangeListener(this);
|
||||
thumbSize.setOnPreferenceChangeListener(this);
|
||||
showTagTranslations.setOnPreferenceChangeListener(this);
|
||||
@@ -70,6 +72,10 @@ public class EhFragment extends BasePreferenceFragment {
|
||||
} else if (Settings.KEY_LIST_MODE.equals(key)) {
|
||||
requireActivity().setResult(Activity.RESULT_OK);
|
||||
return true;
|
||||
} else if (Settings.KEY_LIST_THUMB_SIZE.equals(key)) {
|
||||
Settings.LIST_THUMB_SIZE_INITED = false;
|
||||
requireActivity().setResult(Activity.RESULT_OK);
|
||||
return true;
|
||||
} else if (Settings.KEY_DETAIL_SIZE.equals(key)) {
|
||||
requireActivity().setResult(Activity.RESULT_OK);
|
||||
} else if (Settings.KEY_THUMB_SIZE.equals(key)) {
|
||||
|
||||
@@ -56,7 +56,6 @@ public abstract class BaseScene extends SceneFragment {
|
||||
private View drawerView;
|
||||
@Nullable
|
||||
private SparseArray<Parcelable> drawerViewState;
|
||||
private boolean needWhiteStatusBar = needWhiteStatusBar();
|
||||
|
||||
public void addAboveSnackView(View view) {
|
||||
FragmentActivity activity = getActivity();
|
||||
@@ -118,10 +117,6 @@ public abstract class BaseScene extends SceneFragment {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean needWhiteStatusBar() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getNavCheckedItem() {
|
||||
return 0;
|
||||
}
|
||||
@@ -176,14 +171,6 @@ public abstract class BaseScene extends SceneFragment {
|
||||
drawerView = null;
|
||||
}
|
||||
|
||||
public void setLightStatusBar(boolean set) {
|
||||
var insetsController = getInsetsController();
|
||||
if (insetsController != null) {
|
||||
insetsController.setAppearanceLightStatusBars(set && (requireActivity().getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_YES) <= 0);
|
||||
}
|
||||
needWhiteStatusBar = set;
|
||||
}
|
||||
|
||||
public void onDestroyDrawerView() {
|
||||
}
|
||||
|
||||
@@ -213,7 +200,11 @@ public abstract class BaseScene extends SceneFragment {
|
||||
|
||||
// Hide soft ime
|
||||
hideSoftInput();
|
||||
setLightStatusBar(needWhiteStatusBar);
|
||||
|
||||
insetsController = getInsetsController();
|
||||
if (insetsController != null) {
|
||||
insetsController.setAppearanceLightStatusBars((requireActivity().getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_YES) <= 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -22,21 +22,17 @@ import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.view.Display;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
@@ -45,26 +41,18 @@ import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.PopupMenu;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator;
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
|
||||
|
||||
import com.getkeepsafe.taptargetview.TapTarget;
|
||||
import com.getkeepsafe.taptargetview.TapTargetView;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.DraggableItemAnimator;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractDraggableItemViewHolder;
|
||||
import com.hippo.app.CheckBoxDialogBuilder;
|
||||
import com.hippo.app.EditTextDialogBuilder;
|
||||
import com.hippo.conaco.DataContainer;
|
||||
@@ -85,7 +73,6 @@ import com.hippo.ehviewer.download.DownloadManager;
|
||||
import com.hippo.ehviewer.download.DownloadService;
|
||||
import com.hippo.ehviewer.spider.SpiderDen;
|
||||
import com.hippo.ehviewer.ui.GalleryActivity;
|
||||
import com.hippo.ehviewer.ui.MainActivity;
|
||||
import com.hippo.ehviewer.widget.SimpleRatingView;
|
||||
import com.hippo.io.UniFileInputStreamPipe;
|
||||
import com.hippo.scene.Announcer;
|
||||
@@ -99,7 +86,6 @@ import com.hippo.widget.recyclerview.AutoStaggeredGridLayoutManager;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.FileUtils;
|
||||
import com.hippo.yorozuya.IOUtils;
|
||||
import com.hippo.yorozuya.LayoutUtils;
|
||||
import com.hippo.yorozuya.ObjectUtils;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
import com.hippo.yorozuya.collect.LongList;
|
||||
@@ -390,90 +376,9 @@ public class DownloadsScene extends ToolbarScene
|
||||
|
||||
updateView();
|
||||
|
||||
guide();
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private void guide() {
|
||||
if (Settings.getGuideDownloadThumb() && null != mRecyclerView) {
|
||||
mRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
if (Settings.getGuideDownloadThumb()) {
|
||||
guideDownloadThumb();
|
||||
}
|
||||
if (null != mRecyclerView) {
|
||||
ViewUtils.removeOnGlobalLayoutListener(mRecyclerView.getViewTreeObserver(), this);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
guideDownloadLabels();
|
||||
}
|
||||
}
|
||||
|
||||
private void guideDownloadThumb() {
|
||||
MainActivity activity = getMainActivity();
|
||||
if (null == activity || !Settings.getGuideDownloadThumb() || null == mLayoutManager || null == mRecyclerView) {
|
||||
guideDownloadLabels();
|
||||
return;
|
||||
}
|
||||
int position = mLayoutManager.findFirstCompletelyVisibleItemPositions(null)[0];
|
||||
if (position < 0) {
|
||||
guideDownloadLabels();
|
||||
return;
|
||||
}
|
||||
RecyclerView.ViewHolder holder = mRecyclerView.findViewHolderForAdapterPosition(position);
|
||||
if (null == holder) {
|
||||
guideDownloadLabels();
|
||||
return;
|
||||
}
|
||||
|
||||
TapTargetView.showFor(requireActivity(),
|
||||
TapTarget.forView(((DownloadHolder) holder).thumb,
|
||||
getString(R.string.guide_download_thumb_title),
|
||||
getString(R.string.guide_download_thumb_text))
|
||||
.transparentTarget(true),
|
||||
new TapTargetView.Listener() {
|
||||
@Override
|
||||
public void onTargetDismissed(TapTargetView view, boolean userInitiated) {
|
||||
super.onTargetDismissed(view, userInitiated);
|
||||
Settings.putGuideDownloadThumb(false);
|
||||
guideDownloadLabels();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void guideDownloadLabels() {
|
||||
MainActivity activity = getMainActivity();
|
||||
if (null == activity || !Settings.getGuideDownloadLabels()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Display display = activity.getWindowManager().getDefaultDisplay();
|
||||
Point point = new Point();
|
||||
display.getSize(point);
|
||||
Rect bounds = new Rect(point.x + LayoutUtils.dp2pix(requireContext(), 20),
|
||||
point.y / 3 + LayoutUtils.dp2pix(requireContext(), 20),
|
||||
point.x - LayoutUtils.dp2pix(requireContext(), 20),
|
||||
point.y / 3 - LayoutUtils.dp2pix(requireContext(), 20));
|
||||
|
||||
TapTargetView.showFor(requireActivity(),
|
||||
TapTarget.forBounds(bounds,
|
||||
getString(R.string.guide_download_labels_title),
|
||||
getString(R.string.guide_download_labels_text))
|
||||
.transparentTarget(true),
|
||||
new TapTargetView.Listener() {
|
||||
@Override
|
||||
public void onTargetDismissed(TapTargetView view, boolean userInitiated) {
|
||||
super.onTargetClick(view);
|
||||
Settings.puttGuideDownloadLabels(false);
|
||||
openDrawer(Gravity.RIGHT);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
@@ -639,20 +544,13 @@ public class DownloadsScene extends ToolbarScene
|
||||
|
||||
initLabels();
|
||||
|
||||
RecyclerViewDragDropManager dragDropManager = new RecyclerViewDragDropManager();
|
||||
dragDropManager.setInitiateOnLongPress(true);
|
||||
dragDropManager.setInitiateOnTouch(false);
|
||||
dragDropManager.setDraggingItemAlpha(0.8f);
|
||||
|
||||
mLabelAdapter = new DownloadLabelAdapter(inflater);
|
||||
final EasyRecyclerView recyclerView = view.findViewById(R.id.recycler_view_drawer);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(context));
|
||||
mLabelAdapter.setHasStableIds(true);
|
||||
final GeneralItemAnimator animator = new DraggableItemAnimator();
|
||||
recyclerView.setItemAnimator(animator);
|
||||
recyclerView.setAdapter(dragDropManager.createWrappedAdapter(mLabelAdapter));
|
||||
dragDropManager.attachRecyclerView(recyclerView);
|
||||
|
||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new DownloadLabelItemTouchHelperCallback());
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView);
|
||||
recyclerView.setAdapter(mLabelAdapter);
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -975,7 +873,7 @@ public class DownloadsScene extends ToolbarScene
|
||||
holder.speed.setText(FileUtils.humanReadableByteCount(speed, false) + "/S");
|
||||
}
|
||||
|
||||
private static class DownloadLabelHolder extends AbstractDraggableItemViewHolder {
|
||||
private static class DownloadLabelHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private final TextView label;
|
||||
private final ImageView option;
|
||||
@@ -1054,7 +952,7 @@ public class DownloadsScene extends ToolbarScene
|
||||
}
|
||||
}
|
||||
|
||||
private class DownloadLabelAdapter extends RecyclerView.Adapter<DownloadLabelHolder> implements DraggableItemAdapter<DownloadLabelHolder> {
|
||||
private class DownloadLabelAdapter extends RecyclerView.Adapter<DownloadLabelHolder> {
|
||||
|
||||
private final LayoutInflater mInflater;
|
||||
|
||||
@@ -1108,36 +1006,15 @@ public class DownloadsScene extends ToolbarScene
|
||||
});
|
||||
if (position > 0) {
|
||||
holder.option.setVisibility(View.VISIBLE);
|
||||
holder.itemView.setOnLongClickListener(v -> {
|
||||
holder.option.setOnClickListener(v -> {
|
||||
if (context != null) {
|
||||
PopupMenu popupMenu = new PopupMenu(context, holder.option);
|
||||
popupMenu.inflate(R.menu.download_label_option);
|
||||
popupMenu.show();
|
||||
popupMenu.setOnMenuItemClickListener(item -> {
|
||||
int itemId = item.getItemId();
|
||||
if (itemId == R.id.menu_label_rename) {
|
||||
EditTextDialogBuilder builder = new EditTextDialogBuilder(
|
||||
context, label, getString(R.string.download_labels));
|
||||
builder.setTitle(R.string.rename_label_title);
|
||||
builder.setPositiveButton(android.R.string.ok, null);
|
||||
AlertDialog dialog = builder.show();
|
||||
new RenameLabelDialogHelper(builder, dialog, label);
|
||||
} else if (itemId == R.id.menu_label_remove) {
|
||||
new MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(getString(R.string.delete_label_title))
|
||||
.setMessage(getString(R.string.delete_label_message, label))
|
||||
.setPositiveButton(R.string.delete, (dialog, which) -> {
|
||||
mDownloadManager.deleteLabel(label);
|
||||
mLabels.remove(position);
|
||||
notifyDataSetChanged();
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
EditTextDialogBuilder builder = new EditTextDialogBuilder(
|
||||
context, label, getString(R.string.download_labels));
|
||||
builder.setTitle(R.string.rename_label_title);
|
||||
builder.setPositiveButton(android.R.string.ok, null);
|
||||
AlertDialog dialog = builder.show();
|
||||
new RenameLabelDialogHelper(builder, dialog, label);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
holder.option.setVisibility(View.GONE);
|
||||
@@ -1154,43 +1031,6 @@ public class DownloadsScene extends ToolbarScene
|
||||
public int getItemCount() {
|
||||
return mLabels != null ? mLabels.size() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCheckCanStartDrag(@NonNull DownloadLabelHolder holder, int position, int x, int y) {
|
||||
return position != 0 && x > holder.option.getX() && y > holder.option.getY();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemDraggableRange onGetItemDraggableRange(@NonNull DownloadLabelHolder holder, int position) {
|
||||
return new ItemDraggableRange(1, getItemCount() - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoveItem(int fromPosition, int toPosition) {
|
||||
Context context = getContext();
|
||||
if (null == context || fromPosition == toPosition || toPosition == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
EhApplication.getDownloadManager(context).moveLabel(fromPosition - 1, toPosition - 1);
|
||||
final String item = mLabels.remove(fromPosition);
|
||||
mLabels.add(toPosition, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCheckCanDrop(int draggingPosition, int dropPosition) {
|
||||
return dropPosition != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemDragStarted(int position) {
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemDragFinished(int fromPosition, int toPosition, boolean result) {
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private class DeleteRangeDialogHelper implements DialogInterface.OnClickListener {
|
||||
@@ -1528,4 +1368,43 @@ public class DownloadsScene extends ToolbarScene
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class DownloadLabelItemTouchHelperCallback extends ItemTouchHelper.Callback {
|
||||
|
||||
@Override
|
||||
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
||||
return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT);
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
@Override
|
||||
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
|
||||
int fromPosition = viewHolder.getBindingAdapterPosition();
|
||||
int toPosition = target.getBindingAdapterPosition();
|
||||
Context context = getContext();
|
||||
if (null == context || fromPosition == toPosition || toPosition == 0 || fromPosition == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
EhApplication.getDownloadManager(context).moveLabel(fromPosition - 1, toPosition - 1);
|
||||
final String item = mLabels.remove(fromPosition);
|
||||
mLabels.add(toPosition, item);
|
||||
mLabelAdapter.notifyDataSetChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
@Override
|
||||
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
|
||||
int position = viewHolder.getBindingAdapterPosition();
|
||||
if (mDownloadManager == null)
|
||||
return;
|
||||
if (position != 0) {
|
||||
String label = mLabels.get(position);
|
||||
mDownloadManager.deleteLabel(label);
|
||||
}
|
||||
mLabels.remove(position);
|
||||
mLabelAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,8 +45,6 @@ import androidx.drawerlayout.widget.DrawerLayout;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.getkeepsafe.taptargetview.TapTarget;
|
||||
import com.getkeepsafe.taptargetview.TapTargetView;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.hippo.annotation.Implemented;
|
||||
@@ -311,40 +309,9 @@ public class FavoritesScene extends BaseScene implements
|
||||
mHelper.firstRefresh();
|
||||
}
|
||||
|
||||
guideCollections();
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private void guideCollections() {
|
||||
Activity activity = getMainActivity();
|
||||
if (null == activity || !Settings.getGuideCollections()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Display display = activity.getWindowManager().getDefaultDisplay();
|
||||
Point point = new Point();
|
||||
display.getSize(point);
|
||||
Rect bounds = new Rect(point.x + LayoutUtils.dp2pix(requireContext(), 20),
|
||||
point.y / 3 + LayoutUtils.dp2pix(requireContext(), 20),
|
||||
point.x - LayoutUtils.dp2pix(requireContext(), 20),
|
||||
point.y / 3 - LayoutUtils.dp2pix(requireContext(), 20));
|
||||
|
||||
TapTargetView.showFor(requireActivity(),
|
||||
TapTarget.forBounds(bounds,
|
||||
getString(R.string.guide_collections_title),
|
||||
getString(R.string.guide_collections_text))
|
||||
.transparentTarget(true),
|
||||
new TapTargetView.Listener() {
|
||||
@Override
|
||||
public void onTargetDismissed(TapTargetView view, boolean userInitiated) {
|
||||
super.onTargetClick(view);
|
||||
Settings.putGuideCollections(false);
|
||||
openDrawer(Gravity.RIGHT);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// keyword of mUrlBuilder, fav cat of mUrlBuilder, mFavCatArray.
|
||||
// They changed, call it
|
||||
private void updateSearchBar() {
|
||||
|
||||
@@ -559,12 +559,6 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
|
||||
ViewGroup main = (ViewGroup) ViewUtils.$$(view, R.id.main);
|
||||
ScrollView mainView = (ScrollView) ViewUtils.$$(main, R.id.scroll_view);
|
||||
mainView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
|
||||
if (mActionGroup != null && mHeader != null) {
|
||||
//noinspection IntegerDivisionInFloatingPointContext
|
||||
setLightStatusBar(mActionGroup.getY() - mHeader.findViewById(R.id.header_content).getPaddingTop() / 2 < scrollY);
|
||||
}
|
||||
});
|
||||
View progressView = ViewUtils.$$(main, R.id.progress_view);
|
||||
mTip = (TextView) ViewUtils.$$(main, R.id.tip);
|
||||
mViewTransition = new ViewTransition(mainView, progressView, mTip);
|
||||
@@ -668,11 +662,6 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needWhiteStatusBar() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
@@ -841,19 +830,16 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
|
||||
switch (state) {
|
||||
case STATE_NORMAL:
|
||||
setLightStatusBar(false);
|
||||
// Show mMainView
|
||||
mViewTransition.showView(0, animation);
|
||||
// Show mBelowHeader
|
||||
mViewTransition2.showView(0, animation);
|
||||
break;
|
||||
case STATE_REFRESH:
|
||||
setLightStatusBar(true);
|
||||
// Show mProgressView
|
||||
mViewTransition.showView(1, animation);
|
||||
break;
|
||||
case STATE_REFRESH_HEADER:
|
||||
setLightStatusBar(false);
|
||||
// Show mMainView
|
||||
mViewTransition.showView(0, animation);
|
||||
// Show mProgress
|
||||
@@ -862,7 +848,6 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
default:
|
||||
case STATE_INIT:
|
||||
case STATE_FAILED:
|
||||
setLightStatusBar(true);
|
||||
// Show mFailedView
|
||||
mViewTransition.showView(2, animation);
|
||||
break;
|
||||
|
||||
@@ -18,13 +18,10 @@ package com.hippo.ehviewer.ui.scene;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
@@ -33,10 +30,8 @@ import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.ImageSpan;
|
||||
import android.view.Display;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewGroup;
|
||||
@@ -50,25 +45,17 @@ import androidx.annotation.IntDef;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.PopupMenu;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.WindowInsetsAnimationCompat;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.getkeepsafe.taptargetview.TapTarget;
|
||||
import com.getkeepsafe.taptargetview.TapTargetView;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.DraggableItemAnimator;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractDraggableItemViewHolder;
|
||||
import com.hippo.app.EditTextDialogBuilder;
|
||||
import com.hippo.drawable.AddDeleteDrawable;
|
||||
import com.hippo.drawable.DrawerArrowDrawable;
|
||||
@@ -111,7 +98,6 @@ import com.hippo.widget.FabLayout;
|
||||
import com.hippo.widget.SearchBarMover;
|
||||
import com.hippo.yorozuya.AnimationUtils;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.LayoutUtils;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
import com.hippo.yorozuya.SimpleAnimatorListener;
|
||||
import com.hippo.yorozuya.StringUtils;
|
||||
@@ -634,40 +620,9 @@ public final class GalleryListScene extends BaseScene
|
||||
mHelper.firstRefresh();
|
||||
}
|
||||
|
||||
guideQuickSearch();
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private void guideQuickSearch() {
|
||||
Activity activity = getMainActivity();
|
||||
if (null == activity || !Settings.getGuideQuickSearch()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Display display = activity.getWindowManager().getDefaultDisplay();
|
||||
Point point = new Point();
|
||||
display.getSize(point);
|
||||
Rect bounds = new Rect(point.x + LayoutUtils.dp2pix(requireContext(), 20),
|
||||
point.y / 3 + LayoutUtils.dp2pix(requireContext(), 20),
|
||||
point.x - LayoutUtils.dp2pix(requireContext(), 20),
|
||||
point.y / 3 - LayoutUtils.dp2pix(requireContext(), 20));
|
||||
|
||||
TapTargetView.showFor(requireActivity(),
|
||||
TapTarget.forBounds(bounds,
|
||||
getString(R.string.guide_quick_search_title),
|
||||
getString(R.string.guide_quick_search_text))
|
||||
.transparentTarget(true),
|
||||
new TapTargetView.Listener() {
|
||||
@Override
|
||||
public void onTargetDismissed(TapTargetView view, boolean userInitiated) {
|
||||
super.onTargetClick(view);
|
||||
Settings.putGuideQuickSearch(false);
|
||||
openDrawer(Gravity.RIGHT);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
@@ -786,19 +741,13 @@ public final class GalleryListScene extends BaseScene
|
||||
Context context = getContext();
|
||||
AssertUtils.assertNotNull(context);
|
||||
|
||||
RecyclerViewDragDropManager dragDropManager = new RecyclerViewDragDropManager();
|
||||
dragDropManager.setInitiateOnLongPress(true);
|
||||
dragDropManager.setInitiateOnTouch(false);
|
||||
dragDropManager.setDraggingItemAlpha(0.8f);
|
||||
|
||||
final EasyRecyclerView recyclerView = view.findViewById(R.id.recycler_view_drawer);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(context));
|
||||
final QsDrawerAdapter qsDrawerAdapter = new QsDrawerAdapter(inflater);
|
||||
qsDrawerAdapter.setHasStableIds(true);
|
||||
final GeneralItemAnimator animator = new DraggableItemAnimator();
|
||||
recyclerView.setItemAnimator(animator);
|
||||
recyclerView.setAdapter(dragDropManager.createWrappedAdapter(qsDrawerAdapter));
|
||||
dragDropManager.attachRecyclerView(recyclerView);
|
||||
recyclerView.setAdapter(qsDrawerAdapter);
|
||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new GalleryListQSItemTouchHelperCallback(qsDrawerAdapter));
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView);
|
||||
mQuickSearchList = EhDB.getAllQuickSearch();
|
||||
tip.setText(R.string.quick_search_tip);
|
||||
if (mIsTopList) {
|
||||
@@ -1525,7 +1474,7 @@ public final class GalleryListScene extends BaseScene
|
||||
}
|
||||
}
|
||||
|
||||
private static class QsDrawerHolder extends AbstractDraggableItemViewHolder {
|
||||
private static class QsDrawerHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private final TextView key;
|
||||
private final ImageView option;
|
||||
@@ -1570,7 +1519,7 @@ public final class GalleryListScene extends BaseScene
|
||||
}
|
||||
}
|
||||
|
||||
private class QsDrawerAdapter extends RecyclerView.Adapter<QsDrawerHolder> implements DraggableItemAdapter<QsDrawerHolder> {
|
||||
private class QsDrawerAdapter extends RecyclerView.Adapter<QsDrawerHolder> {
|
||||
|
||||
private final LayoutInflater mInflater;
|
||||
|
||||
@@ -1601,34 +1550,6 @@ public final class GalleryListScene extends BaseScene
|
||||
setState(STATE_NORMAL);
|
||||
closeDrawer(Gravity.RIGHT);
|
||||
});
|
||||
holder.itemView.setOnLongClickListener(v -> {
|
||||
PopupMenu popupMenu = new PopupMenu(requireContext(), holder.option);
|
||||
popupMenu.inflate(R.menu.quicksearch_option);
|
||||
popupMenu.show();
|
||||
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
||||
|
||||
final QuickSearch quickSearch = mQuickSearchList.get(position);
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
if (item.getItemId() == R.id.menu_qs_remove) {
|
||||
new MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(getString(R.string.delete_quick_search_title))
|
||||
.setMessage(getString(R.string.delete_quick_search_message, quickSearch.name))
|
||||
.setPositiveButton(R.string.delete, (dialog, which) -> {
|
||||
EhDB.deleteQuickSearch(quickSearch);
|
||||
mQuickSearchList.remove(position);
|
||||
notifyDataSetChanged();
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
int[] keywords = {11, 12, 13, 15};
|
||||
int[] toplists = {R.string.toplist_alltime, R.string.toplist_pastyear, R.string.toplist_pastmonth, R.string.toplist_yesterday};
|
||||
@@ -1664,44 +1585,6 @@ public final class GalleryListScene extends BaseScene
|
||||
public int getItemCount() {
|
||||
return !mIsTopList ? mQuickSearchList != null ? mQuickSearchList.size() : 0 : 4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCheckCanStartDrag(@NonNull QsDrawerHolder holder, int position, int x, int y) {
|
||||
return !mIsTopList && x > holder.option.getX() && y > holder.option.getY();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemDraggableRange onGetItemDraggableRange(@NonNull QsDrawerHolder holder, int position) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoveItem(int fromPosition, int toPosition) {
|
||||
if (fromPosition == toPosition) {
|
||||
return;
|
||||
}
|
||||
if (null == mQuickSearchList) {
|
||||
return;
|
||||
}
|
||||
EhDB.moveQuickSearch(fromPosition, toPosition);
|
||||
final QuickSearch item = mQuickSearchList.remove(fromPosition);
|
||||
mQuickSearchList.add(toPosition, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCheckCanDrop(int draggingPosition, int dropPosition) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemDragStarted(int position) {
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemDragFinished(int fromPosition, int toPosition, boolean result) {
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class UrlSuggestion extends SearchBar.Suggestion {
|
||||
@@ -1886,4 +1769,47 @@ public final class GalleryListScene extends BaseScene
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class GalleryListQSItemTouchHelperCallback extends ItemTouchHelper.Callback {
|
||||
private final QsDrawerAdapter mAdapter;
|
||||
|
||||
public GalleryListQSItemTouchHelperCallback(QsDrawerAdapter adapter) {
|
||||
mAdapter = adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
||||
return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT);
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
@Override
|
||||
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
|
||||
int fromPosition = viewHolder.getBindingAdapterPosition();
|
||||
int toPosition = target.getBindingAdapterPosition();
|
||||
if (fromPosition == toPosition) {
|
||||
return false;
|
||||
}
|
||||
if (null == mQuickSearchList) {
|
||||
return false;
|
||||
}
|
||||
EhDB.moveQuickSearch(fromPosition, toPosition);
|
||||
final QuickSearch item = mQuickSearchList.remove(fromPosition);
|
||||
mQuickSearchList.add(toPosition, item);
|
||||
mAdapter.notifyDataSetChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
@Override
|
||||
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
|
||||
if (mQuickSearchList == null)
|
||||
return;
|
||||
int position = viewHolder.getBindingAdapterPosition();
|
||||
final QuickSearch quickSearch = mQuickSearchList.get(position);
|
||||
EhDB.deleteQuickSearch(quickSearch);
|
||||
mQuickSearchList.remove(position);
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,20 +36,11 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.SwipeDismissItemAnimator;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemAdapter;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemConstants;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultAction;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionDefault;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionRemoveItem;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchActionGuardManager;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractSwipeableItemViewHolder;
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView;
|
||||
import com.hippo.easyrecyclerview.FastScroller;
|
||||
import com.hippo.easyrecyclerview.HandlerDrawable;
|
||||
@@ -206,17 +197,9 @@ public class HistoryScene extends ToolbarScene {
|
||||
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
|
||||
mTip.setCompoundDrawables(null, drawable, null, null);
|
||||
|
||||
RecyclerViewTouchActionGuardManager guardManager = new RecyclerViewTouchActionGuardManager();
|
||||
guardManager.setInterceptVerticalScrollingWhileAnimationRunning(true);
|
||||
guardManager.setEnabled(true);
|
||||
RecyclerViewSwipeManager swipeManager = new RecyclerViewSwipeManager();
|
||||
mAdapter = new HistoryAdapter();
|
||||
mAdapter.setHasStableIds(true);
|
||||
mAdapter = swipeManager.createWrappedAdapter(mAdapter);
|
||||
mRecyclerView.setAdapter(mAdapter);
|
||||
final GeneralItemAnimator animator = new SwipeDismissItemAnimator();
|
||||
animator.setSupportsChangeAnimations(false);
|
||||
mRecyclerView.setItemAnimator(animator);
|
||||
AutoStaggeredGridLayoutManager layoutManager = new AutoStaggeredGridLayoutManager(
|
||||
0, StaggeredGridLayoutManager.VERTICAL);
|
||||
layoutManager.setColumnSize(resources.getDimensionPixelOffset(Settings.getDetailSizeResId()));
|
||||
@@ -229,9 +212,8 @@ public class HistoryScene extends ToolbarScene {
|
||||
int paddingV = resources.getDimensionPixelOffset(R.dimen.gallery_list_margin_v);
|
||||
MarginItemDecoration decoration = new MarginItemDecoration(interval, paddingH, paddingV, paddingH, paddingV);
|
||||
mRecyclerView.addItemDecoration(decoration);
|
||||
guardManager.attachRecyclerView(mRecyclerView);
|
||||
swipeManager.attachRecyclerView(mRecyclerView);
|
||||
|
||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new HistoryItemTouchHelperCallback());
|
||||
itemTouchHelper.attachToRecyclerView(mRecyclerView);
|
||||
mFastScroller.attachToRecyclerView(mRecyclerView);
|
||||
HandlerDrawable handlerDrawable = new HandlerDrawable();
|
||||
handlerDrawable.setColor(ResourcesKt.resolveColor(getTheme(), com.google.android.material.R.attr.colorPrimary));
|
||||
@@ -502,7 +484,7 @@ public class HistoryScene extends ToolbarScene {
|
||||
}
|
||||
}
|
||||
|
||||
private static class HistoryHolder extends AbstractSwipeableItemViewHolder {
|
||||
private static class HistoryHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
public final View card;
|
||||
public final LoadImageView thumb;
|
||||
@@ -529,12 +511,6 @@ public class HistoryScene extends ToolbarScene {
|
||||
pages = itemView.findViewById(R.id.pages);
|
||||
downloaded = itemView.findViewById(R.id.downloaded);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View getSwipeableContainerView() {
|
||||
return card;
|
||||
}
|
||||
}
|
||||
|
||||
private class MoveDialogHelper implements DialogInterface.OnClickListener {
|
||||
@@ -570,8 +546,7 @@ public class HistoryScene extends ToolbarScene {
|
||||
}
|
||||
}
|
||||
|
||||
private class HistoryAdapter extends RecyclerView.Adapter<HistoryHolder>
|
||||
implements SwipeableItemAdapter<HistoryHolder> {
|
||||
private class HistoryAdapter extends RecyclerView.Adapter<HistoryHolder> {
|
||||
|
||||
private final LayoutInflater mInflater;
|
||||
private final int mListThumbWidth;
|
||||
@@ -649,55 +624,30 @@ public class HistoryScene extends ToolbarScene {
|
||||
public int getItemCount() {
|
||||
return null != mLazyList ? mLazyList.size() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onGetSwipeReactionType(@NonNull HistoryHolder holder, int position, int x, int y) {
|
||||
return SwipeableItemConstants.REACTION_CAN_SWIPE_LEFT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwipeItemStarted(@NonNull HistoryHolder holder, int position) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetSwipeBackground(@NonNull HistoryHolder holder, int position, int type) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SwipeResultAction onSwipeItem(@NonNull HistoryHolder holder, int position, int result) {
|
||||
switch (result) {
|
||||
case SwipeableItemConstants.RESULT_SWIPED_LEFT:
|
||||
return new SwipeResultActionClear(position);
|
||||
case SwipeableItemConstants.RESULT_SWIPED_RIGHT:
|
||||
case SwipeableItemConstants.RESULT_CANCELED:
|
||||
case SwipeableItemConstants.RESULT_NONE:
|
||||
case SwipeableItemConstants.RESULT_SWIPED_DOWN:
|
||||
case SwipeableItemConstants.RESULT_SWIPED_UP:
|
||||
default:
|
||||
return new SwipeResultActionDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SwipeResultActionClear extends SwipeResultActionRemoveItem {
|
||||
private class HistoryItemTouchHelperCallback extends ItemTouchHelper.Callback {
|
||||
|
||||
private final int mPosition;
|
||||
|
||||
protected SwipeResultActionClear(int position) {
|
||||
mPosition = position;
|
||||
@Override
|
||||
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
||||
return makeMovementFlags(0, ItemTouchHelper.LEFT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPerformAction() {
|
||||
super.onPerformAction();
|
||||
if (null == mLazyList || null == mAdapter) {
|
||||
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
|
||||
int mPosition = viewHolder.getBindingAdapterPosition();
|
||||
if (null == mLazyList || mAdapter == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
HistoryInfo info = mLazyList.get(mPosition);
|
||||
EhDB.deleteHistoryInfo(info);
|
||||
updateLazyList();
|
||||
mAdapter.notifyDataSetChanged();
|
||||
mAdapter.notifyItemRemoved(mPosition);
|
||||
updateView(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,12 +87,6 @@ public abstract class ToolbarScene extends BaseScene {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needWhiteStatusBar() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public int getMenuResId() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -53,11 +53,6 @@ public class FixedThumb extends LoadImageView {
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
public void setFix(float minAspect, float maxAspect) {
|
||||
this.minAspect = minAspect;
|
||||
this.maxAspect = maxAspect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPreSetImageDrawable(Drawable drawable, boolean isTarget) {
|
||||
if (alwaysCutAndScale) {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.hippo.ehviewer.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import com.hippo.ehviewer.Settings
|
||||
|
||||
class ResizeableFixedThumb @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : FixedThumb(context, attrs) {
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
setMeasuredDimension(Settings.getListThumbSize() * 2, Settings.getListThumbSize() * 3)
|
||||
}
|
||||
}
|
||||
@@ -245,7 +245,7 @@ public abstract class GalleryProvider {
|
||||
}
|
||||
|
||||
public void add(Integer key, ImageWrapper value) {
|
||||
if (value.getFormat() != Image.FORMAT_ANIMATED && value.obtain()) {
|
||||
if (value.getFormat() != Image.FORMAT_GIF && value.obtain()) {
|
||||
put(key, value);
|
||||
}
|
||||
}
|
||||
@@ -253,7 +253,7 @@ public abstract class GalleryProvider {
|
||||
@Override
|
||||
protected int sizeOf(Integer key, ImageWrapper value) {
|
||||
int size = value.getWidth() * value.getHeight() * 4;
|
||||
if (value.getFormat() == Image.FORMAT_ANIMATED) {
|
||||
if (value.getFormat() == Image.FORMAT_GIF) {
|
||||
size *= 5;
|
||||
}
|
||||
return size;
|
||||
|
||||
@@ -153,6 +153,12 @@ public class ImageTexture implements Texture, Animatable {
|
||||
}
|
||||
|
||||
mTiles = list.toArray(new Tile[list.size()]);
|
||||
|
||||
if (!mImage.isCompleted()) {
|
||||
Runnable runnable = new AnimateRunnable();
|
||||
mAnimateRunnable = runnable;
|
||||
sThreadExecutor.execute(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
private static Tile obtainSmallTile() {
|
||||
@@ -233,7 +239,7 @@ public class ImageTexture implements Texture, Animatable {
|
||||
}
|
||||
|
||||
boolean end = mReleased.get() || mImage.isImageRecycled() || mNeedRelease.get() ||
|
||||
(mImage.getFormat() == 0) || mRunning.get();
|
||||
(mImage.isCompleted() && mImage.getFrameCount() <= 1) || mRunning.get();
|
||||
|
||||
synchronized (mImage) {
|
||||
mImageBusy = false;
|
||||
@@ -591,11 +597,25 @@ public class ImageTexture implements Texture, Animatable {
|
||||
mImageBusy = true;
|
||||
}
|
||||
|
||||
if (!mImage.isCompleted()) {
|
||||
try {
|
||||
sPVLock.p();
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore
|
||||
}
|
||||
if (!mNeedRelease.get()) {
|
||||
mImage.complete();
|
||||
}
|
||||
sPVLock.v();
|
||||
}
|
||||
|
||||
int frameCount = mImage.getFrameCount();
|
||||
|
||||
synchronized (mImage) {
|
||||
// Release image
|
||||
mImageBusy = false;
|
||||
// Check need release, frameCount <= 1
|
||||
if (mNeedRelease.get() || mImage.getFormat() == 0) {
|
||||
if (mNeedRelease.get() || frameCount <= 1) {
|
||||
mAnimateRunnable = null;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -122,6 +122,22 @@ public class ImageWrapper {
|
||||
return mCut.height();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Image#complete()
|
||||
*/
|
||||
public boolean complete() {
|
||||
synchronized (mImage) {
|
||||
return mImage.complete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Image#isCompleted()
|
||||
*/
|
||||
public boolean isCompleted() {
|
||||
return mImage.isCompleted();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Image#render(int, int, Bitmap, int, int, int, int, boolean, int)
|
||||
*/
|
||||
@@ -152,6 +168,13 @@ public class ImageWrapper {
|
||||
return mImage.getDelay();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Image#getFrameCount()
|
||||
*/
|
||||
public int getFrameCount() {
|
||||
return mImage.getFrameCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Image#isOpaque()
|
||||
*/
|
||||
|
||||
@@ -21,18 +21,49 @@
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<group
|
||||
android:scaleX="0.37947997"
|
||||
android:scaleY="0.37947997"
|
||||
android:translateX="35.1"
|
||||
android:translateY="39.584126">
|
||||
<path
|
||||
android:fillColor="@color/ic_launcher_foreground"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M39.893,68.066L39.893,75.977L0,75.977L0,4.59L39.893,4.59L39.893,12.5L8.984,12.5L8.984,34.766L38.086,34.766L38.086,42.578L8.984,42.578L8.984,68.066L39.893,68.066Z" />
|
||||
<path
|
||||
android:fillColor="@color/ic_launcher_foreground"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M99.609,75.977L90.918,75.977L90.918,41.699A23.436,23.436 0,0 0,90.662 38.109Q90.055,34.207 88.013,31.958Q85.629,29.334 81.028,28.863A20.84,20.84 0,0 0,78.906 28.76A23.134,23.134 0,0 0,74.45 29.16Q71.926,29.656 69.994,30.767A11.122,11.122 0,0 0,66.968 33.301A13.32,13.32 0,0 0,64.756 37.276Q63.399,40.971 63.213,46.495A53.517,53.517 0,0 0,63.184 48.291L63.184,75.977L54.395,75.977L54.395,0L63.184,0L63.184,22.266A66.935,66.935 0,0 1,63.115 25.383Q63.043,26.914 62.897,28.251A33.547,33.547 0,0 1,62.695 29.785L63.281,29.785A16.11,16.11 0,0 1,69.394 24.035A19.448,19.448 0,0 1,70.068 23.682A20.88,20.88 0,0 1,77.292 21.622A25.763,25.763 0,0 1,79.98 21.484A32.643,32.643 0,0 1,85.483 21.917Q91.249,22.905 94.678,26.147Q98.472,29.735 99.347,36.631A35.274,35.274 0,0 1,99.609 41.064L99.609,75.977Z" />
|
||||
</group>
|
||||
<group android:scaleX="0.7425"
|
||||
android:scaleY="0.7425"
|
||||
android:translateX="30.68"
|
||||
android:translateY="30.68">
|
||||
<path
|
||||
android:pathData="M11,9H34"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FBBD06"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M11,54H23"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FBBD06"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M11,9L11,54"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#35AA53"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M11,31L52,31"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FBBD06"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M52,31L52,54"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#4586F3"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M34,21L34,54"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#EB4334"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M15,35H11C8.791,35 7,33.209 7,31C7,28.791 8.791,27 11,27H15V35Z"
|
||||
android:fillColor="#FBBD06"
|
||||
android:fillType="evenOdd"/>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -42,7 +42,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:padding="@dimen/keyline_margin"
|
||||
app:srcCompat="@mipmap/ic_launcher" />
|
||||
app:srcCompat="@drawable/ic_launcher_foreground" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -77,10 +77,11 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.hippo.widget.LoadImageView
|
||||
<com.hippo.ehviewer.widget.FixedThumb
|
||||
android:id="@+id/thumb"
|
||||
android:layout_width="@dimen/gallery_detail_thumb_width"
|
||||
android:layout_height="@dimen/gallery_detail_thumb_height" />
|
||||
android:layout_height="@dimen/gallery_detail_thumb_height"
|
||||
app:alwaysCutAndScale="true"/>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
|
||||
@@ -29,10 +29,10 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.hippo.ehviewer.widget.FixedThumb
|
||||
<com.hippo.ehviewer.widget.ResizeableFixedThumb
|
||||
android:id="@+id/thumb"
|
||||
android:layout_width="@dimen/gallery_list_thumb_width"
|
||||
android:layout_height="@dimen/gallery_list_thumb_height"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:alwaysCutAndScale="true"
|
||||
app:retryType="longClick" />
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/card"
|
||||
style="@style/Widget.Material3.CardView.Elevated"
|
||||
android:layout_width="match_parent"
|
||||
@@ -31,15 +32,15 @@
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/thumb_card"
|
||||
style="@style/Widget.Material3.CardView.Filled"
|
||||
android:layout_width="@dimen/gallery_list_thumb_width"
|
||||
android:layout_height="@dimen/gallery_list_thumb_height">
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.hippo.ehviewer.widget.FixedThumb
|
||||
<com.hippo.ehviewer.widget.ResizeableFixedThumb
|
||||
android:id="@+id/thumb"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:alwaysCutAndScale="true"
|
||||
app:retryType="click" />
|
||||
app:retryType="click"/>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:padding="@dimen/keyline_margin"
|
||||
app:srcCompat="@mipmap/ic_launcher" />
|
||||
app:srcCompat="@drawable/ic_launcher_foreground" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/username_layout"
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
~ You should have received a copy of the GNU General Public License along with EhViewer.
|
||||
~ If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<background android:drawable="@android:color/white"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -15,7 +15,8 @@
|
||||
~ You should have received a copy of the GNU General Public License along with EhViewer.
|
||||
~ If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<background android:drawable="@android:color/white"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 838 B |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 832 B After Width: | Height: | Size: 666 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.1 KiB |
@@ -106,7 +106,6 @@
|
||||
<string name="content_layout_restore_tip">点击重试</string>
|
||||
<string name="write_rationale">EhViewer 将申请设备存储空间访问权限以便下载画廊。您愿意授权吗?</string>
|
||||
<string name="you_rejected_me">好痛</string>
|
||||
<string name="analytics_explain">您同意让开发者收集您的设备信息与使用情况吗?这将对开发有很大帮助并将为您启用应用内更新。<br><br>数据统计服务由 Microsoft AppCenter 提供,其隐私政策可在 <a href="https://privacy.microsoft.com/zh-cn/privacystatement">https://privacy.microsoft.com/zh-cn/privacystatement</a> 查看。<br><br>若您同意参与数据统计,您的设备信息与应用使用情况将会被记录,其包括但不限于:Android API 版本、设备型号、语言所在地、本应用版本号、会话时长、应用功能使用次数。开发者承诺以下信息并不会被记录:电话号码、电子邮件地址、IMEI。<br><br>在您同意参与数据统计之前,您的信息不会被记录。<br><br>被记录的数据会由 Microsoft AppCenter 进行分析,分析报表仅可被本应用开发者访问。</string>
|
||||
<string name="get_it">知道了</string>
|
||||
<string name="accept">接受</string>
|
||||
<string name="reject">拒绝</string>
|
||||
@@ -756,8 +755,8 @@
|
||||
<string name="pick_new_download_location">选择新路径</string>
|
||||
<string name="dont_show_again">不再显示</string>
|
||||
<string name="open_settings">打开设置</string>
|
||||
<string name="app_link_not_verified_message">对于 Android 12 及更新的版本,您需要手动添加链接到已验证链接才能在 EhViewer 中打开 E-Hentai 链接。</string>
|
||||
<string name="app_link_not_verified_title">应用链接未验证</string>
|
||||
<string name="app_link_not_verified_message">对于您的设备,我们强烈建议您使用 1.8 版本。1.7 版本仅为 Android 11 及以下版本设计</string>
|
||||
<string name="app_link_not_verified_title">升级 App 版本</string>
|
||||
<string name="open_by_default">默认打开</string>
|
||||
|
||||
<string name="show_library_version">依赖库之版本</string>
|
||||
@@ -769,4 +768,5 @@
|
||||
<string name="archive_need_passwd">归档文件需要密码</string>
|
||||
<string name="passwd_wrong">密码错误</string>
|
||||
<string name="passwd_cannot_be_empty">密码不能为空</string>
|
||||
<string name="list_tile_thumb_size">详情模式下缩略图大小</string>
|
||||
</resources>
|
||||
@@ -19,8 +19,8 @@
|
||||
<string name="app_name">EhViewer</string>
|
||||
|
||||
<!-- Theme -->
|
||||
<string name="theme_light">亮色主題</string>
|
||||
<string name="theme_dark">暗色主題</string>
|
||||
<string name="theme_light">淺色主題</string>
|
||||
<string name="theme_dark">深色主題</string>
|
||||
<string name="theme_black">黑色主題</string>
|
||||
|
||||
<string name="homepage">首頁</string>
|
||||
@@ -144,7 +144,7 @@
|
||||
<string name="search_specify_tag">指定標籤</string>
|
||||
<string name="search_enable_advance">啟用進階選項</string>
|
||||
|
||||
<string name="search_tip">一般搜尋:就是搜尋。\n\n訂閱搜素:在訂閱中搜索。\n\n指定上傳者:列出該上傳者上傳的圖庫。其他選項會被忽略。\n\n指定標籤:列出含有該標籤的圖庫。其他選項會被忽略。</string>
|
||||
<string name="search_tip">一般搜尋:就是搜尋。\n\n訂閱搜尋:在訂閱中搜尋。\n\n指定上傳者:列出該上傳者上傳的圖庫。其他選項會被忽略。\n\n指定標籤:列出含有該標籤的圖庫。其他選項會被忽略。</string>
|
||||
|
||||
<string name="search_sname">搜尋圖庫</string>
|
||||
<string name="search_stags">搜尋圖庫的標籤</string>
|
||||
@@ -183,7 +183,7 @@
|
||||
<string name="delete_quick_search_message">刪除“%s”嗎?</string>
|
||||
<string name="no_quick_search">快速搜尋會顯示在這裡</string>
|
||||
<string name="add_quick_search_tip">圖庫列表的狀態將被儲存為快速搜尋。如果你想儲存搜尋面板的狀態,請先進行搜尋。</string>
|
||||
<string name="readme">讀我檔</string>
|
||||
<string name="readme">ReadMe</string>
|
||||
<string name="image_search_not_quick_search">無法將圖片搜尋加入快速搜尋</string>
|
||||
<string name="duplicate_quick_search">已存在相同的快速搜尋,名稱為“%s”。</string>
|
||||
<string name="duplicate_name">已經由重複的名稱了</string>
|
||||
@@ -234,7 +234,10 @@
|
||||
<string name="add_to_favorite_failure">無法新增至收藏</string>
|
||||
<string name="remove_from_favorite_failure">無法從收藏中移除</string>
|
||||
<string name="filter_the_uploader">把上傳者“%s”加入濾除列表?</string>
|
||||
<string name="filter_the_tag">隱藏標籤“%s”?</string>
|
||||
<string name="filter_added">已經新增篩選條件</string>
|
||||
<string name="newer_version_avaliable">此圖庫有新版本可以使用。</string>
|
||||
<string name="newer_version_title">%1$s, 增加於 %2$s</string>
|
||||
|
||||
<!-- Rating -->
|
||||
<string name="rating10">太神啦!</string>
|
||||
@@ -349,6 +352,7 @@
|
||||
<string name="rename_label_title">重新命名標籤</string>
|
||||
<string name="delete_label_title">刪除標籤</string>
|
||||
<string name="delete_label_message">將 “%s”刪除?</string>
|
||||
<string name="rename_label">重新命名</string>
|
||||
|
||||
<!-- History -->
|
||||
<string name="no_history">歷史紀錄將會顯示在這裡</string>
|
||||
@@ -373,6 +377,7 @@
|
||||
<string name="set_pattern_protection">設定圖形解鎖</string>
|
||||
<string name="set_pattern_protection_tip">繪製圖案來設定繪圖解鎖\n留白以取消圖案保護</string>
|
||||
<string name="set">設定</string>
|
||||
<string name="enable_biometric">允許使用生物識別解鎖</string>
|
||||
|
||||
<!-- UConfig Activity -->
|
||||
<string name="u_config">EHentai 設定</string>
|
||||
@@ -400,6 +405,7 @@
|
||||
<string name="page_menu_refresh">@string/refresh</string>
|
||||
<string name="page_menu_share">@string/share</string>
|
||||
<string name="page_menu_save">儲存</string>
|
||||
<string name="page_menu_save_to">儲存到…</string>
|
||||
<string name="page_menu_add_bookmark">新增書籤</string>
|
||||
<string name="gallery_menu_title">選單</string>
|
||||
<string name="share_image">分享圖片</string>
|
||||
@@ -422,8 +428,8 @@
|
||||
<string name="settings_my_tags">我的標籤</string>
|
||||
<string name="settings_my_tags_summary">EHentai 網站上的我的標籤</string>
|
||||
<string name="settings_eh_theme">主題</string>
|
||||
<string name="settings_eh_theme_light">亮色</string>
|
||||
<string name="settings_eh_theme_dark">暗色</string>
|
||||
<string name="settings_eh_theme_light">淺色</string>
|
||||
<string name="settings_eh_theme_dark">深色</string>
|
||||
<string name="settings_eh_theme_black">黑色</string>
|
||||
<string name="settings_eh_apply_nav_bar_theme_color">導航欄應用主題配色</string>
|
||||
<string name="settings_eh_gallery_site">圖庫站台</string>
|
||||
@@ -465,6 +471,7 @@
|
||||
<string name="settings_read_screen_rotation_default">系統預設</string>
|
||||
<string name="settings_read_screen_rotation_portrait">直向</string>
|
||||
<string name="settings_read_screen_rotation_landscape">橫向</string>
|
||||
<string name="settings_read_screen_rotation_sensor">自動旋轉</string>
|
||||
<string name="settings_read_reading_direction">閱讀方向</string>
|
||||
<string name="settings_read_reading_direction_left_to_right">由左到右</string>
|
||||
<string name="settings_read_reading_direction_right_to_Left">由右到左</string>
|
||||
@@ -490,6 +497,7 @@
|
||||
<string name="settings_read_reading_fullscreen">全螢幕</string>
|
||||
<string name="settings_read_custom_screen_lightness">自訂螢幕亮度</string>
|
||||
<string name="settings_read_screen_lightness">螢幕亮度</string>
|
||||
<string name="settings_read_reverse_volume">翻轉音量鍵</string>
|
||||
|
||||
<string name="settings_download">下載</string>
|
||||
<string name="settings_download_download_location">下載路徑</string>
|
||||
@@ -525,7 +533,7 @@
|
||||
<string name="settings_advanced">進階</string>
|
||||
<string name="settings_advanced_save_parse_error_body">解析失敗時儲存網頁的內容</string>
|
||||
<string name="settings_advanced_save_parse_error_body_summary">網頁內容可能含有敏感的隱私資料</string>
|
||||
<string name="settings_advanced_save_crash_log">應用崩潰時儲存錯誤日誌</string>
|
||||
<string name="settings_advanced_save_crash_log">應用程式崩潰時儲存錯誤日誌</string>
|
||||
<string name="settings_advanced_save_crash_log_summary">錯誤日誌可以幫助開發者修正問題</string>
|
||||
<string name="settings_advanced_dump_logcat">傾印log</string>
|
||||
<string name="settings_advanced_dump_logcat_summary">儲存log紀錄至外接儲存裝置中</string>
|
||||
@@ -591,7 +599,7 @@
|
||||
<string name="rewrite">改寫</string>
|
||||
<string name="language_japanese">日語</string>
|
||||
<string name="language_english">英語</string>
|
||||
<string name="language_chinese">漢語</string>
|
||||
<string name="language_chinese">中文</string>
|
||||
<string name="language_dutch">荷蘭語</string>
|
||||
<string name="language_french">法語</string>
|
||||
<string name="language_german">德語</string>
|
||||
@@ -702,6 +710,63 @@
|
||||
<string name="settings_privacy">隱私</string>
|
||||
<string name="settings_privacy_secure">不允許螢幕擷取</string>
|
||||
<string name="settings_privacy_secure_summary">啟用後,將無法對本程式進行螢幕擷取,同時,將不會在系統任務切換器中顯示該程式的預覽內容,重新啟動本程式以套用此變更</string>
|
||||
|
||||
<string name="download_service">下載服務</string>
|
||||
<string name="favorite_name">收藏圖庫</string>
|
||||
|
||||
<string name="black_dark_theme">純黑深色主題</string>
|
||||
<string name="download_filter">過濾</string>
|
||||
<string name="download_all">全部</string>
|
||||
<string name="download_start_all_reversed">全部開始(倒序)</string>
|
||||
<string name="settings_download_download_delay">下載延時</string>
|
||||
<string name="settings_download_download_delay_summary">每次下載延時 %s 毫秒</string>
|
||||
|
||||
<string name="no_browser_installed">請安裝一個瀏覽器。</string>
|
||||
<string name="toplist_alltime">從始至終</string>
|
||||
<string name="toplist_pastyear">過去一年</string>
|
||||
<string name="toplist_pastmonth">過去一個月</string>
|
||||
<string name="toplist_yesterday">昨日</string>
|
||||
<string name="toplist">排行</string>
|
||||
<string name="tag_vote_down">不,這不是</string>
|
||||
<string name="tag_vote_up">是的,這很正確</string>
|
||||
<string name="tag_vote_successfully">投票成功</string>
|
||||
<string name="from_clipboard">從剪貼板匯入</string>
|
||||
<string name="from_clipboard_error">未在剪貼板中發現 Cookies。</string>
|
||||
<string name="delete_search_history">從搜索記錄中刪除“%s”?</string>
|
||||
<string name="settings_advanced_dns_over_http_title">安全 DNS(DoH)</string>
|
||||
<string name="settings_advanced_dns_over_http_summary">解決某些地區的 DNS 汙染問題</string>
|
||||
<string name="settings_advanced_domain_fronting_title">域名前置</string>
|
||||
<string name="settings_advanced_domain_fronting_summary">繞過 SNI 封鎖</string>
|
||||
<string name="read_theme">色彩主題</string>
|
||||
<string name="follow_app">跟隨應用</string>
|
||||
<string name="settings_e_ink_mode_title">墨水屏模式</string>
|
||||
<string name="settings_e_ink_mode_summary">為墨水屏做一些最佳化</string>
|
||||
<string name="settings_advanced_clear_memory_cache_done">完成記憶體快取清理</string>
|
||||
<string name="action_add_tag">增加標籤</string>
|
||||
<string name="action_add_tag_tip">增加新標籤,用逗號「,」分割</string>
|
||||
<string name="error_no_webview">WebView 不可用</string>
|
||||
<string name="comment_user_uploader">%s (上傳者)</string>
|
||||
<string name="settings_eh_metered_network_warning">流量計費網路警告</string>
|
||||
<string name="metered_network_warning">正在使用流量計費網路</string>
|
||||
<string name="read_from">從第 %d 頁閱讀</string>
|
||||
<string name="settings_request_news_title">啟動時請求新聞頁面</string>
|
||||
<string name="copy_trans">複製翻譯</string>
|
||||
<string name="reset_download_location">恢復預設路徑</string>
|
||||
<string name="pick_new_download_location">選擇新路徑</string>
|
||||
<string name="dont_show_again">不再顯示</string>
|
||||
<string name="open_settings">打開設置</string>
|
||||
<string name="app_link_not_verified_message">對於 Android 12 及更新的版本,您需要手動增加連結到已驗證連結才能在 EhViewer 中打開 E-Hentai 連結。</string>
|
||||
<string name="app_link_not_verified_title">應用連結未驗證</string>
|
||||
<string name="open_by_default">預設打開</string>
|
||||
|
||||
<string name="show_library_version">依賴庫之版本</string>
|
||||
<string name="settings_privacy_require_unlock">需要解鎖</string>
|
||||
<string name="proxy_type_name">代理類型</string>
|
||||
<string name="filter_label">屏蔽項類型</string>
|
||||
|
||||
<string name="archive_passwd">歸檔文件密碼</string>
|
||||
<string name="archive_need_passwd">歸檔文件需要密碼</string>
|
||||
<string name="passwd_wrong">密碼錯誤</string>
|
||||
<string name="passwd_cannot_be_empty">密碼不能為空</string>
|
||||
|
||||
</resources>
|
||||
@@ -134,10 +134,6 @@
|
||||
<attr name="url" format="string" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="SeekBarPreference">
|
||||
<attr name="max" format="integer" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="FastScroller">
|
||||
<attr name="handler" format="reference" />
|
||||
<attr name="draggable" format="boolean" />
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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 GeneralPublic 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/>.
|
||||
-->
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#E2E0D0</color>
|
||||
<color name="ic_launcher_foreground">#5C0D11</color>
|
||||
</resources>
|
||||
@@ -31,8 +31,6 @@
|
||||
<dimen name="rating_size">16dp</dimen>
|
||||
<dimen name="rating_interval">1dp</dimen>
|
||||
|
||||
<dimen name="gallery_list_thumb_height">120dp</dimen>
|
||||
<dimen name="gallery_list_thumb_width">80dp</dimen>
|
||||
<!-- 4dp - 4dp, 4dp for card padding -->
|
||||
<dimen name="gallery_list_interval">0dp</dimen>
|
||||
<!-- 8dp - 4dp, 4dp for card padding -->
|
||||
|
||||
@@ -148,7 +148,6 @@
|
||||
<string name="content_layout_restore_tip">Tap to retry</string>
|
||||
<string name="write_rationale">EhViewer will request WRITE_EXTERNAL_STORAGE permission for downloading gallery. Do you want to grant the permission?</string>
|
||||
<string name="you_rejected_me">It hurts</string>
|
||||
<string name="analytics_explain">Would you mind if the developer collected your device information and your usage? It will help the developer a lot.<br><br>Data analysis service is provided by Microsoft AppCenter. Its privacy policy can be found at <a href="https://privacy.microsoft.com/zh-cn/privacystatement">https://privacy.microsoft.com/zh-cn/privacystatement</a>.<br><br>If you agree to join data analysis, your device information and your usage will be collected, including but not limited to Android API version, equipment model, language residence, the application version, conversion time, function times. The developer promise that these information will NOT be collected, including phone number, e-mail address, IMEI.<br><br>Your information will not be collected until you agree to join data analysis.<br><br>The collected information will be analyzed by Microsoft AppCenter. The analysis report can only be viewed by the developer of this application.</string>
|
||||
<string name="get_it">Got it</string>
|
||||
<string name="accept">Accept</string>
|
||||
<string name="reject">Reject</string>
|
||||
@@ -820,8 +819,8 @@
|
||||
<string name="pick_new_download_location">Pick a new location</string>
|
||||
<string name="dont_show_again">Don\'t show again</string>
|
||||
<string name="open_settings">Open settings</string>
|
||||
<string name="app_link_not_verified_message">For Android 12 and newer, you need to manually add link to verified links in order to open E-Hentai links in EhViewer.</string>
|
||||
<string name="app_link_not_verified_title">App links not verified</string>
|
||||
<string name="app_link_not_verified_message">For you device, We strongly recommend you to use version 1.8. version 1.7 is only made for Android 11 and below .</string>
|
||||
<string name="app_link_not_verified_title">Update App Version</string>
|
||||
<string name="open_by_default">Open by default</string>
|
||||
|
||||
<string name="show_library_version">Library Version</string>
|
||||
@@ -833,4 +832,5 @@
|
||||
<string name="archive_need_passwd">Archive need password</string>
|
||||
<string name="passwd_wrong">Password Wrong</string>
|
||||
<string name="passwd_cannot_be_empty">Password can\'t be empty</string>
|
||||
<string name="list_tile_thumb_size">Thumb size in thumb mode</string>
|
||||
</resources>
|
||||
@@ -82,6 +82,15 @@
|
||||
app:entryValues="@array/list_mode_entry_values"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SeekBarPreference
|
||||
android:defaultValue="40"
|
||||
android:key="list_tile_size"
|
||||
android:title="@string/list_tile_thumb_size"
|
||||
android:max="60"
|
||||
app:iconSpaceReserved="false"
|
||||
app:min="30"
|
||||
app:showSeekBarValue="true" />
|
||||
|
||||
<rikka.preference.SimpleMenuPreference
|
||||
android:defaultValue="0"
|
||||
android:key="detail_size"
|
||||
|
||||
@@ -122,8 +122,8 @@
|
||||
android:dependency="custom_screen_lightness"
|
||||
android:key="screen_lightness"
|
||||
android:title="@string/settings_read_screen_lightness"
|
||||
android:max="200"
|
||||
app:iconSpaceReserved="false"
|
||||
app:max="200"
|
||||
app:showSeekBarValue="true" />
|
||||
|
||||
</PreferenceScreen>
|
||||
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="646" height="250" id="svg8502" version="1.1" inkscape:version="0.91 r13725" sodipodi:docname="get-it-on-github.svg" inkscape:export-filename="/home/nixdorf/Lokaler Speicher/Build/andOTP/assets/badges/get-it-on-github.png" inkscape:export-xdpi="90" inkscape:export-ydpi="90">
|
||||
<defs id="defs8504" />
|
||||
<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="0.50000001" inkscape:cx="-223.13537" inkscape:cy="546.37524" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="true" fit-margin-top="0" fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0" inkscape:window-width="1920" inkscape:window-height="1021" inkscape:window-x="0" inkscape:window-y="31" inkscape:window-maximized="1" />
|
||||
<metadata id="metadata8507">
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
<dc:creator>
|
||||
<cc:Agent>
|
||||
<dc:title>Andrew Nayenko</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:creator>
|
||||
<dc:publisher>
|
||||
<cc:Agent>
|
||||
<dc:title>https://f-droid.org</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:publisher>
|
||||
<cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/3.0/" />
|
||||
<dc:contributor>
|
||||
<cc:Agent>
|
||||
<dc:title />
|
||||
</cc:Agent>
|
||||
</dc:contributor>
|
||||
</cc:Work>
|
||||
<cc:License rdf:about="http://creativecommons.org/licenses/by-sa/3.0/">
|
||||
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction" />
|
||||
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution" />
|
||||
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice" />
|
||||
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution" />
|
||||
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
|
||||
<cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike" />
|
||||
</cc:License>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" transform="translate(-289,-312.36218)">
|
||||
<rect style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#a6a6a6;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate" id="rect3007" width="560" height="164" x="332" y="355.36218" rx="20" ry="20" />
|
||||
<text sodipodi:linespacing="100%" id="text3013" y="402.36697" x="508.95129" style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.39519119px;line-height:100%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;enable-background:accumulate" xml:space="preserve"><tspan y="402.36697" x="508.95129" id="tspan3015" sodipodi:role="line" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:34.125px;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans'">GET IT ON</tspan></text>
|
||||
<text xml:space="preserve" style="color:black;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:29.70882034px;line-height:100%;font-family:Rokkitt;-inkscape-font-specification:'Rokkitt Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:white;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;enable-background:accumulate" x="508.21259" y="489.36108" id="text5902" sodipodi:linespacing="100%"><tspan sodipodi:role="line" id="tspan5904" x="508.21259" y="489.36108" style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:87.5px;line-height:100%;font-family:'Roboto Slab';-inkscape-font-specification:'Roboto Slab Bold';text-align:start;writing-mode:lr-tb;text-anchor:start;fill:white;fill-opacity:1">GitHub</tspan></text>
|
||||
<g transform="matrix(1.0017681,0,0,1.0017681,68.770756,92.660249)" id="g5136">
|
||||
<path style="fill:white;fill-opacity:1;fill-rule:evenodd;stroke:none" d="m 350.60937,282.4375 c -33.34765,0 -60.38671,27.03516 -60.38671,60.38672 0,26.67969 17.30078,49.3164 41.29687,57.30078 3.01563,0.55859 4.125,-1.30859 4.125,-2.90625 0,-1.4375 -0.0547,-6.19531 -0.082,-11.24219 -16.80078,3.65235 -20.34375,-7.125 -20.34375,-7.125 -2.75,-6.98047 -6.70703,-8.83594 -6.70703,-8.83594 -5.48047,-3.74609 0.41406,-3.67187 0.41406,-3.67187 6.0625,0.42578 9.25781,6.22266 9.25781,6.22266 5.38282,9.23437 14.125,6.5664 17.57032,5.02343 0.54296,-3.90234 2.10937,-6.57031 3.83593,-8.07812 -13.41406,-1.52735 -27.51562,-6.70313 -27.51562,-29.84375 0,-6.59375 2.35937,-11.98047 6.22265,-16.20703 -0.625,-1.52344 -2.6914,-7.66407 0.58594,-15.98047 0,0 5.07031,-1.625 16.60938,6.1875 4.82031,-1.33594 9.98437,-2.00781 15.11718,-2.03125 5.13282,0.0234 10.30079,0.69531 15.12891,2.03125 11.52344,-7.8125 16.58984,-6.1875 16.58984,-6.1875 3.28516,8.3164 1.21875,14.45703 0.58985,15.98047 3.87109,4.22656 6.21484,9.61328 6.21484,16.20703 0,23.19531 -14.125,28.30078 -27.57422,29.79687 2.16797,1.875 4.09766,5.55078 4.09766,11.1836 0,8.07812 -0.0703,14.58203 -0.0703,16.57031 0,1.60937 1.08593,3.49219 4.14843,2.89844 23.98047,-7.9961 41.26172,-30.6211 41.26172,-57.29297 0,-33.35156 -27.03515,-60.38672 -60.38672,-60.38672" id="path4937" inkscape:connector-curvature="0" />
|
||||
<path style="fill:white;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 313.09375,369.14062 c -0.13281,0.30079 -0.60547,0.38672 -1.03516,0.1836 -0.4375,-0.19922 -0.68359,-0.60547 -0.54297,-0.91016 0.12891,-0.30859 0.60157,-0.39062 1.03907,-0.1875 0.4414,0.19922 0.6914,0.61328 0.53906,0.91406" id="path4939" inkscape:connector-curvature="0" />
|
||||
<path style="fill:white;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 315.53906,371.86719 c -0.28906,0.26562 -0.85156,0.14453 -1.23437,-0.27735 -0.39453,-0.42187 -0.46875,-0.98437 -0.17578,-1.25781 0.29687,-0.26562 0.84375,-0.14062 1.23828,0.28125 0.39453,0.42578 0.47265,0.98438 0.17187,1.25391" id="path4941" inkscape:connector-curvature="0" />
|
||||
<path style="fill:white;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 317.92187,375.34375 c -0.37109,0.25781 -0.97656,0.0156 -1.35156,-0.51953 -0.37109,-0.53906 -0.37109,-1.17969 0.008,-1.4375 0.375,-0.25781 0.97266,-0.0274 1.35157,0.5039 0.36718,0.54688 0.36718,1.19141 -0.008,1.45313" id="path4943" inkscape:connector-curvature="0" />
|
||||
<path style="fill:white;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 321.18359,378.70312 c -0.33203,0.36719 -1.03906,0.26954 -1.55468,-0.23046 -0.52735,-0.48438 -0.67579,-1.17579 -0.34375,-1.54297 0.33593,-0.36328 1.04687,-0.26172 1.5664,0.23047 0.52344,0.48828 0.6836,1.18359 0.33203,1.54296" id="path4945-3" inkscape:connector-curvature="0" />
|
||||
<path style="fill:white;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 325.68359,380.65625 c -0.14843,0.47266 -0.82812,0.6875 -1.51172,0.48828 -0.68359,-0.20703 -1.1289,-0.76172 -0.99218,-1.24219 0.14453,-0.47265 0.82422,-0.69922 1.51562,-0.48437 0.67969,0.20703 1.12891,0.75781 0.98828,1.23828" id="path4947-6" inkscape:connector-curvature="0" />
|
||||
<path style="fill:white;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 330.625,381.01953 c 0.0156,0.49609 -0.5625,0.91016 -1.28125,0.91797 -0.72266,0.0156 -1.30859,-0.38672 -1.31641,-0.875 0,-0.50391 0.57032,-0.91406 1.28907,-0.92578 0.71875,-0.0156 1.30859,0.38672 1.30859,0.88281" id="path4949" inkscape:connector-curvature="0" />
|
||||
<path style="fill:white;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 335.22266,380.23437 c 0.0859,0.48829 -0.41407,0.98438 -1.125,1.11719 -0.70313,0.12891 -1.35157,-0.17187 -1.44141,-0.65234 -0.0859,-0.5 0.42187,-0.9961 1.12109,-1.125 0.71485,-0.125 1.35547,0.16797 1.44532,0.66015" id="path4951-7" inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 24 KiB |
@@ -0,0 +1,10 @@
|
||||
<svg width="108" height="108" viewBox="0 0 108 108" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="108" height="108" rx="10" fill="white"/>
|
||||
<path d="M32 31H55" stroke="#FBBD06" stroke-width="8" stroke-linecap="round"/>
|
||||
<path d="M32 76H44" stroke="#FBBD06" stroke-width="8" stroke-linecap="round"/>
|
||||
<line x1="32" y1="31" x2="32" y2="76" stroke="#35AA53" stroke-width="8" stroke-linecap="round"/>
|
||||
<line x1="32" y1="53" x2="73" y2="53" stroke="#FBBD06" stroke-width="8" stroke-linecap="round"/>
|
||||
<line x1="73" y1="53" x2="73" y2="76" stroke="#4586F3" stroke-width="8" stroke-linecap="round"/>
|
||||
<path d="M55 43L55 76" stroke="#EB4334" stroke-width="8" stroke-linecap="round"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M36 57H32C29.7909 57 28 55.2091 28 53C28 50.7909 29.7909 49 32 49H36V57Z" fill="#FBBD06"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 832 B |
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
id 'com.android.application' version '7.2.1' apply false
|
||||
id 'com.android.application' version '7.2.2' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
<p align="right">
|
||||
<strong>English<strong>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-cn.md">
|
||||
简体中文
|
||||
</a>
|
||||
<span> | <span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-tw.md">
|
||||
正體中文
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<h1 align="center">
|
||||
<img src="../../art/launcher_icon-web.svg" width="200">
|
||||
<br>EhViewer<br>
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml">
|
||||
<img src="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml/badge.svg" alt="Github Actions">
|
||||
</a>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/Ehviewer-Overhauled/Ehviewer" alt="LICENSE">
|
||||
</a>
|
||||
<a href="https://www.codefactor.io/repository/github/Ehviewer-Overhauled/ehviewer">
|
||||
<img src="https://www.codefactor.io/repository/github/Ehviewer-Overhauled/ehviewer/badge" alt="CodeFactor">
|
||||
</a>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases">
|
||||
<img src="https://img.shields.io/github/v/release/Ehviewer-Overhauled/ehviewer?include_prereleases" alt="Release">
|
||||
</a>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/issues">
|
||||
<img src="https://img.shields.io/github/issues/Ehviewer-Overhauled/ehviewer" alt="Issues">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
<h3>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/en.md#description">
|
||||
Description
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/en.md#download">
|
||||
Download
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/en.md#screenshot">
|
||||
Screenshot
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/en.md#thanks">
|
||||
Thanks
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/en.md#license">
|
||||
License
|
||||
</a>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
|
||||
# Description
|
||||
|
||||
EhViewer fork dedicated to lightweight and high-performance
|
||||
|
||||
with Material Design 3 and Dynamic Color Support
|
||||
|
||||
# Download
|
||||
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases"><img alt="Get it on Github" src="../../art/get-it-on-github.svg" width="200px"/></a>
|
||||
|
||||
App is divided into two versions: 1.7 and 1.8 . Version 1.8 support Android 12 and above, also the develop mainline. Version 1.7 support Android 11 and below, drop down some spicetify functions for Android 12 to make app useable on lower Android System
|
||||
|
||||
# Screenshot
|
||||
|
||||

|
||||

|
||||
|
||||
# Thanks
|
||||
|
||||
Here is the libraries
|
||||
|
||||
- [AOSP&AndroidX](http://source.android.com/)
|
||||
- [FullDraggableDrawer](https://github.com/PureWriter/FullDraggableDrawer)
|
||||
- [GnuTLS](https://gnutls.org/)
|
||||
- [material-components-android](https://github.com/material-components/material-components-android)
|
||||
- [material-design-icons](https://github.com/google/material-design-icons)
|
||||
- [Apache Commons Lang](https://commons.apache.org/proper/commons-lang/)
|
||||
- [apng](http://apng.sourceforge.net/)
|
||||
- [giflib](http://giflib.sourceforge.net)
|
||||
- [greenDAO](https://github.com/greenrobot/greenDAO)
|
||||
- [jsoup](https://github.com/jhy/jsoup)
|
||||
- [libjpeg-turbo](https://libjpeg-turbo.org/)
|
||||
- [libpng](http://www.libpng.org/pub/png/libpng.html)
|
||||
- [okhttp](https://github.com/square/okhttp)
|
||||
- [roaster](https://github.com/forge/roaster)
|
||||
- [libarchive](http://www.libarchive.org/)
|
||||
- [XZ Utils](https://tukaani.org/xz/)
|
||||
|
||||
|
||||
# License
|
||||
|
||||
Copyright 2014-2019 Hippo Seven
|
||||
Copyright 2020-2022 NekoInverter
|
||||
Copyright 2022 Tarsin Norbin
|
||||
|
||||
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/>.
|
||||
@@ -0,0 +1,110 @@
|
||||
<p align="right">
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/en.md">
|
||||
English
|
||||
</a>
|
||||
<span> | </span>
|
||||
<strong>简体中文<strong>
|
||||
<span> | <span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-tw.md">
|
||||
正體中文
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<h1 align="center">
|
||||
<img src="../../art/launcher_icon-web.svg" width="200">
|
||||
<br>EhViewer<br>
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml">
|
||||
<img src="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml/badge.svg" alt="Github Actions">
|
||||
</a>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/Ehviewer-Overhauled/Ehviewer" alt="LICENSE">
|
||||
</a>
|
||||
<a href="https://www.codefactor.io/repository/github/Ehviewer-Overhauled/ehviewer">
|
||||
<img src="https://www.codefactor.io/repository/github/Ehviewer-Overhauled/ehviewer/badge" alt="CodeFactor">
|
||||
</a>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases">
|
||||
<img src="https://img.shields.io/github/v/release/Ehviewer-Overhauled/ehviewer?include_prereleases" alt="Release">
|
||||
</a>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/issues">
|
||||
<img src="https://img.shields.io/github/issues/Ehviewer-Overhauled/ehviewer" alt="Issues">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
<h3>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-cn.md#描述">
|
||||
描述
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-cn.md#下载">
|
||||
下载
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-cn.md#截图">
|
||||
截图
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-cn.md#感谢">
|
||||
感谢
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-cn.md#许可证">
|
||||
许可证
|
||||
</a>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
|
||||
# 描述
|
||||
|
||||
致力于轻量化与良好性能表现的 EhViewer 分支
|
||||
|
||||
已针对性适配 Material Design 3 及其动态取色系统
|
||||
|
||||
# 下载
|
||||
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases"><img alt="Get it on Github" src="../../art/get-it-on-github.svg" width="200px"/></a>
|
||||
|
||||
App 分为 1.8 与 1.7 两个版本。1.8 版本适配 Android 12 及以上系统,为开发主线;1.7 版本适配 Android 11 及以下版本,削减了 Android 12 及以上系统的独有功能,使其能在低版本 Android 上正常工作
|
||||
|
||||
# 截图
|
||||
|
||||

|
||||

|
||||
|
||||
# 感谢
|
||||
|
||||
本项目受到了诸多开源项目的帮助
|
||||
|
||||
- [AOSP&AndroidX](http://source.android.com/)
|
||||
- [FullDraggableDrawer](https://github.com/PureWriter/FullDraggableDrawer)
|
||||
- [GnuTLS](https://gnutls.org/)
|
||||
- [material-components-android](https://github.com/material-components/material-components-android)
|
||||
- [material-design-icons](https://github.com/google/material-design-icons)
|
||||
- [Apache Commons Lang](https://commons.apache.org/proper/commons-lang/)
|
||||
- [apng](http://apng.sourceforge.net/)
|
||||
- [giflib](http://giflib.sourceforge.net)
|
||||
- [greenDAO](https://github.com/greenrobot/greenDAO)
|
||||
- [jsoup](https://github.com/jhy/jsoup)
|
||||
- [libjpeg-turbo](https://libjpeg-turbo.org/)
|
||||
- [libpng](http://www.libpng.org/pub/png/libpng.html)
|
||||
- [okhttp](https://github.com/square/okhttp)
|
||||
- [roaster](https://github.com/forge/roaster)
|
||||
- [libarchive](http://www.libarchive.org/)
|
||||
- [XZ Utils](https://tukaani.org/xz/)
|
||||
|
||||
|
||||
# 许可证
|
||||
|
||||
Copyright 2014-2019 Hippo Seven
|
||||
Copyright 2020-2022 NekoInverter
|
||||
Copyright 2022 Tarsin Norbin
|
||||
|
||||
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/>.
|
||||
@@ -0,0 +1,110 @@
|
||||
<p align="right">
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/en.md">
|
||||
English
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-cn.md">
|
||||
简体中文
|
||||
</a>
|
||||
<span> | <span>
|
||||
<strong>正體中文<strong>
|
||||
</p>
|
||||
|
||||
<h1 align="center">
|
||||
<img src="../../art/launcher_icon-web.svg" width="200">
|
||||
<br>EhViewer<br>
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml">
|
||||
<img src="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml/badge.svg" alt="Github Actions">
|
||||
</a>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/Ehviewer-Overhauled/Ehviewer" alt="LICENSE">
|
||||
</a>
|
||||
<a href="https://www.codefactor.io/repository/github/Ehviewer-Overhauled/ehviewer">
|
||||
<img src="https://www.codefactor.io/repository/github/Ehviewer-Overhauled/ehviewer/badge" alt="CodeFactor">
|
||||
</a>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases">
|
||||
<img src="https://img.shields.io/github/v/release/Ehviewer-Overhauled/ehviewer?include_prereleases" alt="Release">
|
||||
</a>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/issues">
|
||||
<img src="https://img.shields.io/github/issues/Ehviewer-Overhauled/ehviewer" alt="Issues">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
<h3>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-tw.md#說明">
|
||||
說明
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-tw.md#下載">
|
||||
下載
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-tw.md#截圖">
|
||||
截圖
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-tw.md#感謝">
|
||||
感謝
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-tw.md#許可證書">
|
||||
許可證書
|
||||
</a>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
|
||||
# 說明
|
||||
|
||||
致力於輕量化與良好性能表現的 EhViewer 分支
|
||||
|
||||
已針對性適配 Material Design 3 及其動態取色系統
|
||||
|
||||
# 下載
|
||||
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases"><img alt="Get it on Github" src="../../art/get-it-on-github.svg" width="200px"/></a>
|
||||
|
||||
App 分為 1.8 與 1.7 兩個版本。1.8 版本適配 Android 12 及以上系統,為開發主線;1.7 版本適配 Android 11 及以下版本,削減了 Android 12 及以上系統的獨有功能,使其能在低版本 Android 上正常工作
|
||||
|
||||
# 截圖
|
||||
|
||||

|
||||

|
||||
|
||||
# 感謝
|
||||
|
||||
本項目受到了諸多開源項目的幫助
|
||||
|
||||
- [AOSP&AndroidX](http://source.android.com/)
|
||||
- [FullDraggableDrawer](https://github.com/PureWriter/FullDraggableDrawer)
|
||||
- [GnuTLS](https://gnutls.org/)
|
||||
- [material-components-android](https://github.com/material-components/material-components-android)
|
||||
- [material-design-icons](https://github.com/google/material-design-icons)
|
||||
- [Apache Commons Lang](https://commons.apache.org/proper/commons-lang/)
|
||||
- [apng](http://apng.sourceforge.net/)
|
||||
- [giflib](http://giflib.sourceforge.net)
|
||||
- [greenDAO](https://github.com/greenrobot/greenDAO)
|
||||
- [jsoup](https://github.com/jhy/jsoup)
|
||||
- [libjpeg-turbo](https://libjpeg-turbo.org/)
|
||||
- [libpng](http://www.libpng.org/pub/png/libpng.html)
|
||||
- [okhttp](https://github.com/square/okhttp)
|
||||
- [roaster](https://github.com/forge/roaster)
|
||||
- [libarchive](http://www.libarchive.org/)
|
||||
- [XZ Utils](https://tukaani.org/xz/)
|
||||
|
||||
|
||||
# 許可證書
|
||||
|
||||
Copyright 2014-2019 Hippo Seven
|
||||
Copyright 2020-2022 NekoInverter
|
||||
Copyright 2022 Tarsin Norbin
|
||||
|
||||
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/>.
|
||||
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -9,7 +9,7 @@ android {
|
||||
ndkVersion "25.0.8775105"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 31
|
||||
minSdkVersion 26
|
||||
targetSdkVersion 33
|
||||
consumerProguardFiles 'proguard-rules.pro'
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ public class Native {
|
||||
|
||||
public static native String getliblzmaVersion();
|
||||
|
||||
public static native String getlibjpeg_turboVersion();
|
||||
|
||||
public static native String getzlibVersion();
|
||||
|
||||
public static native String getnettleVersion();
|
||||
|
||||
@@ -47,7 +47,7 @@ public class UriArchiveAccessor {
|
||||
|
||||
private native int openArchive(long addr, long size);
|
||||
|
||||
public native long extracttoOutputStream(int index);
|
||||
public native void extracttoOutputStream(int index, int fd);
|
||||
|
||||
public native boolean needPassword();
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ import android.graphics.Bitmap;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.InputStream;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@@ -29,9 +28,31 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
* 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;
|
||||
/**
|
||||
* Unknown image format
|
||||
*/
|
||||
public static final int FORMAT_UNKNOWN = -1;
|
||||
|
||||
/**
|
||||
* Plain image format, for {@code Image} from {@link #create(Bitmap)}
|
||||
*/
|
||||
public static final int FORMAT_PLAIN = 0;
|
||||
|
||||
/**
|
||||
* JPEG image format
|
||||
*/
|
||||
public static final int FORMAT_JPEG = 1;
|
||||
|
||||
/**
|
||||
* PNG image format
|
||||
*/
|
||||
public static final int FORMAT_PNG = 2;
|
||||
|
||||
/**
|
||||
* GIF image format
|
||||
*/
|
||||
public static final int FORMAT_GIF = 3;
|
||||
|
||||
private static final AtomicInteger sImageCount = new AtomicInteger();
|
||||
private final int mFormat;
|
||||
@@ -53,24 +74,11 @@ public final class Image {
|
||||
* Decode image from {@code InputStream}
|
||||
*/
|
||||
@Nullable
|
||||
public static Image decode(FileDescriptor fd, boolean partially) {
|
||||
return nativeDecode(fd, partially);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode image from {@code InputStream}
|
||||
*/
|
||||
@Nullable
|
||||
public static Image decode(Integer fd, boolean partially) {
|
||||
return nativeDecodeFdInt(fd, partially);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode image from {@code InputStream}
|
||||
*/
|
||||
@Nullable
|
||||
public static Image decodeAddr(Long addr, boolean partially) {
|
||||
return nativeDecodeAddr(addr, partially);
|
||||
public static Image decode(InputStream is, boolean partially) {
|
||||
if (!(is instanceof BufferedInputStream)) {
|
||||
is = new BufferedInputStream(is);
|
||||
}
|
||||
return nativeDecode(is, partially);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,16 +97,31 @@ public final class Image {
|
||||
return sImageCount.get();
|
||||
}
|
||||
|
||||
private static native Image nativeDecode(FileDescriptor fd, boolean partially);
|
||||
/**
|
||||
* Return all supported image formats, exclude {@link #FORMAT_PLAIN}
|
||||
*/
|
||||
public static int[] getSupportedImageFormats() {
|
||||
return nativeGetSupportedImageFormats();
|
||||
}
|
||||
|
||||
private static native Image nativeDecodeFdInt(int fd, boolean partially);
|
||||
/**
|
||||
* Return decoder description of the image format,
|
||||
* {@code null} for invalid image format.
|
||||
*/
|
||||
public static String getDecoderDescription(int format) {
|
||||
return nativeGetDecoderDescription(format);
|
||||
}
|
||||
|
||||
private static native Image nativeDecodeAddr(long addr, boolean partially);
|
||||
private static native Image nativeDecode(InputStream is, boolean partially);
|
||||
|
||||
private static native Image nativeCreate(Bitmap bitmap);
|
||||
|
||||
private static native int nativeGetByteCount(long nativePtr, int format);
|
||||
|
||||
private static native boolean nativeComplete(long nativePtr, int format);
|
||||
|
||||
private static native boolean nativeIsCompleted(long nativePtr, int format);
|
||||
|
||||
private static native void nativeRender(long nativePtr, int format,
|
||||
int srcX, int srcY, Bitmap dst, int dstX, int dstY,
|
||||
int width, int height, boolean fillBlank, int defaultColor);
|
||||
@@ -110,6 +133,8 @@ public final class Image {
|
||||
|
||||
private static native int nativeGetDelay(long nativePtr, int format);
|
||||
|
||||
private static native int nativeFrameCount(long nativePtr, int format);
|
||||
|
||||
private static native boolean nativeIsOpaque(long nativePtr, int format);
|
||||
|
||||
private static native boolean nativeIsGray(long nativePtr, int format, int error);
|
||||
@@ -118,6 +143,10 @@ public final class Image {
|
||||
|
||||
private static native void nativeRecycle(long nativePtr, int format);
|
||||
|
||||
private static native int[] nativeGetSupportedImageFormats();
|
||||
|
||||
private static native String nativeGetDecoderDescription(int format);
|
||||
|
||||
/**
|
||||
* Return the format of the image
|
||||
*/
|
||||
@@ -157,6 +186,22 @@ public final class Image {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete the image decoding
|
||||
*/
|
||||
public boolean complete() {
|
||||
checkRecycled();
|
||||
return nativeComplete(mNativePtr, mFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the image decoding completed
|
||||
*/
|
||||
public boolean isCompleted() {
|
||||
checkRecycled();
|
||||
return nativeIsCompleted(mNativePtr, mFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the image to {@code Bitmap}
|
||||
*/
|
||||
@@ -194,6 +239,14 @@ public final class Image {
|
||||
return delay <= 10 ? 100 : delay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return frame count. 1 for non-animation image
|
||||
*/
|
||||
public int getFrameCount() {
|
||||
checkRecycled();
|
||||
return nativeFrameCount(mNativePtr, mFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return is the image opaque
|
||||
*/
|
||||
|
||||
@@ -28,7 +28,6 @@ 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;
|
||||
@@ -46,6 +45,7 @@ public final class ImageBitmap implements Animatable, Runnable {
|
||||
private final int mFormat;
|
||||
private final boolean mIsOpaque;
|
||||
private final int mByteCount;
|
||||
private final int mFrameCount;
|
||||
private final Set<WeakReference<Callback>> mCallbackSet = new LinkedHashSet<>();
|
||||
@Nullable
|
||||
private Image mImage;
|
||||
@@ -61,8 +61,9 @@ public final class ImageBitmap implements Animatable, Runnable {
|
||||
mFormat = image.getFormat();
|
||||
mIsOpaque = image.isOpaque();
|
||||
mByteCount = image.getByteCount();
|
||||
mFrameCount = image.getFrameCount();
|
||||
|
||||
if (mFormat == 1) {
|
||||
if (mFrameCount > 1) {
|
||||
// For animated image, save image object
|
||||
mImage = image;
|
||||
} else {
|
||||
@@ -72,18 +73,19 @@ public final class ImageBitmap implements Animatable, Runnable {
|
||||
}
|
||||
|
||||
private ImageBitmap(@NonNull Bitmap bitmap) {
|
||||
mFormat = Image.FORMAT_NORMAL;
|
||||
mFormat = Image.FORMAT_PLAIN;
|
||||
mBitmap = bitmap;
|
||||
mIsOpaque = !bitmap.hasAlpha();
|
||||
mByteCount = bitmap.getRowBytes() * bitmap.getHeight();
|
||||
mFrameCount = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode {@code InputStream}, then create image.
|
||||
*/
|
||||
@Nullable
|
||||
public static ImageBitmap decode(@NonNull FileDescriptor fd) {
|
||||
Image image = Image.decode(fd, false);
|
||||
public static ImageBitmap decode(@NonNull InputStream is) {
|
||||
Image image = Image.decode(is, false);
|
||||
if (image != null) {
|
||||
return create(image);
|
||||
} else {
|
||||
@@ -235,6 +237,13 @@ public final class ImageBitmap implements Animatable, Runnable {
|
||||
return mImage != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return image frame count
|
||||
*/
|
||||
public int getFrameCount() {
|
||||
return mFrameCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw image to canvas
|
||||
*/
|
||||
|
||||
@@ -121,37 +121,35 @@ Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *_, jobject thiz, jlong add
|
||||
return r;
|
||||
}
|
||||
|
||||
typedef struct Memarea {
|
||||
void* buffer;
|
||||
long size;
|
||||
} Memarea;
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_extracttoOutputStream(JNIEnv *_, jobject thiz, jint index) {
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_extracttoOutputStream(JNIEnv *_, jobject thiz, jint index,
|
||||
jint fd) {
|
||||
struct archive_entry *entry;
|
||||
size_t size;
|
||||
int ret;
|
||||
la_int64_t offset;
|
||||
const void *buff;
|
||||
if (!arc || index < cur_index) {
|
||||
archive_alloc();
|
||||
cur_index = 0;
|
||||
}
|
||||
Memarea* memarea = malloc(sizeof(Memarea));
|
||||
while (archive_read_next_header(arc, &entry) == ARCHIVE_OK) {
|
||||
if (!filename_is_playable_file(archive_entry_pathname(entry)))
|
||||
continue;
|
||||
if (cur_index++ == index) {
|
||||
memarea->size = archive_entry_size(entry);
|
||||
memarea->buffer = malloc(memarea->size);
|
||||
for (;;) {
|
||||
ret = archive_read_data(arc, memarea->buffer, memarea->size);
|
||||
if (ret < 0) {
|
||||
ret = archive_read_data_block(arc, &buff, &size, &offset);
|
||||
if (ret == ARCHIVE_EOF)
|
||||
break;
|
||||
if (ret != ARCHIVE_OK) {
|
||||
LOGE("%s%s", "Archive read failed:", archive_error_string(arc));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
write(fd, buff, size);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (jlong) memarea;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
@@ -174,7 +172,6 @@ Java_com_hippo_UriArchiveAccessor_providePassword(JNIEnv *_, jobject thiz, jstri
|
||||
free(passwd);
|
||||
passwd = calloc(len, sizeof(char));
|
||||
strcpy(passwd, (*env)->GetStringUTFChars(env, str, NULL));
|
||||
LOGI("%s%s", "Passwd is ", passwd);
|
||||
archive_alloc();
|
||||
while (archive_read_next_header(arc, &entry) == ARCHIVE_OK) {
|
||||
if (!filename_is_playable_file(archive_entry_pathname(entry)))
|
||||
|
||||
@@ -13,24 +13,9 @@
|
||||
# limitations under the License.
|
||||
|
||||
cmake_minimum_required(VERSION 3.4.1)
|
||||
project(image C)
|
||||
|
||||
set(IMAGE_SOURCES
|
||||
image.c
|
||||
image_utils.c
|
||||
filter/clahe.c
|
||||
filter/gray.c
|
||||
java_wrapper.c
|
||||
)
|
||||
set(IMAGE_INCLUDES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
set(IMAGE_LIBRARIES
|
||||
log
|
||||
jnigraphics
|
||||
GLESv2
|
||||
)
|
||||
|
||||
add_library(image STATIC ${IMAGE_SOURCES})
|
||||
target_include_directories(image PRIVATE ${IMAGE_INCLUDES})
|
||||
target_link_libraries(image PRIVATE ${IMAGE_LIBRARIES})
|
||||
add_subdirectory(image)
|
||||
add_subdirectory(stream)
|
||||
add_subdirectory(libjpeg-turbo)
|
||||
add_subdirectory(libpng)
|
||||
add_subdirectory(giflib)
|
||||
@@ -1,186 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 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.
|
||||
*/
|
||||
|
||||
#include "clahe.h"
|
||||
#include "pixel.h"
|
||||
#include "../utils.h"
|
||||
#include "../log.h"
|
||||
|
||||
#define TILE_BLOCK 8
|
||||
#define LIMIT_MULTIPLE 4
|
||||
#define CL_MAX_LOOP_NUMBER 4
|
||||
|
||||
static BYTE clamp(int t) {
|
||||
t = t > 255 ? 255 : t;
|
||||
t = t < 0 ? 0 : t;
|
||||
return (BYTE) t;
|
||||
}
|
||||
|
||||
void IMAGE_clahe(void *data, int width, int height, bool to_gray) {
|
||||
if (width < TILE_BLOCK || height < TILE_BLOCK) {
|
||||
return;
|
||||
}
|
||||
|
||||
PIXEL *pixels = data;
|
||||
|
||||
if (to_gray) {
|
||||
for (int i = 0; i < width * height; ++i) {
|
||||
PIXEL *p = pixels + i;
|
||||
int t1 = (int) (p->c1 * 0.299f + p->c2 * 0.587f + p->c3 * 0.114f);
|
||||
p->c1 = clamp(t1);
|
||||
}
|
||||
} else {
|
||||
// RGBA to YUVA
|
||||
for (int i = 0; i < width * height; ++i) {
|
||||
PIXEL *p = pixels + i;
|
||||
int t1 = (int) (p->c1 * 0.299f + p->c2 * 0.587f + p->c3 * 0.114f);
|
||||
int t2 = (int) (p->c1 * -0.169f + p->c2 * -0.331f + p->c3 * 0.5f + 128);
|
||||
int t3 = (int) (p->c1 * 0.5f + p->c2 * -0.419f + p->c3 * -0.081f + 128);
|
||||
p->c1 = clamp(t1);
|
||||
p->c2 = clamp(t2);
|
||||
p->c3 = clamp(t3);
|
||||
}
|
||||
}
|
||||
|
||||
int tile_width = width / TILE_BLOCK;
|
||||
int tile_height = height / TILE_BLOCK;
|
||||
int histogram[TILE_BLOCK * TILE_BLOCK][256] = {0};
|
||||
float accumulation[TILE_BLOCK * TILE_BLOCK][256] = {0.0f};
|
||||
|
||||
// Build histogram and accumulation
|
||||
for (int i = 0; i < TILE_BLOCK; i++) {
|
||||
for (int j = 0; j < TILE_BLOCK; j++) {
|
||||
int x_start = tile_width * i;
|
||||
int x_end = i == TILE_BLOCK - 1 ? width : x_start + tile_width;
|
||||
int y_start = tile_height * j;
|
||||
int y_end = j == TILE_BLOCK - 1 ? height : y_start + tile_height;
|
||||
int tile_size = (x_end - x_start) * (y_end - y_start);
|
||||
int tile_index = i + TILE_BLOCK * j;
|
||||
|
||||
// Count
|
||||
for (int x = x_start; x < x_end; x++) {
|
||||
for (int y = y_start; y < y_end; y++) {
|
||||
PIXEL *p = pixels + x + y * width;
|
||||
histogram[tile_index][p->c1] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// CL
|
||||
int limit = tile_size * LIMIT_MULTIPLE / 256;
|
||||
for (int m = 0; m < CL_MAX_LOOP_NUMBER; m++) {
|
||||
int steal = 0;
|
||||
for (int n = 0; n < 256; n++) {
|
||||
if (histogram[tile_index][n] > limit) {
|
||||
steal += histogram[tile_index][n] - limit;
|
||||
histogram[tile_index][n] = limit;
|
||||
}
|
||||
}
|
||||
int bonus = steal / 256;
|
||||
if (bonus == 0 && steal != 0 && m == 0) {
|
||||
bonus = 1;
|
||||
}
|
||||
if (bonus == 0) {
|
||||
break;
|
||||
}
|
||||
for (int n = 0; n < 256; n++) {
|
||||
histogram[tile_index][n] += bonus;
|
||||
}
|
||||
}
|
||||
|
||||
// accumulation
|
||||
for (int n = 0; n < 256; n++) {
|
||||
accumulation[tile_index][n] = (float) histogram[tile_index][n] / tile_size;
|
||||
if (n != 0) {
|
||||
accumulation[tile_index][n] += accumulation[tile_index][n - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Interpolation
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (int y = 0; y < height; y++) {
|
||||
PIXEL *p = pixels + x + y * width;
|
||||
// top-left corner
|
||||
if (x <= tile_width / 2 && y <= tile_height / 2) {
|
||||
p->c1 = clamp((int) (accumulation[0][p->c1] * 255));
|
||||
continue;
|
||||
}
|
||||
// top-right corner
|
||||
if (x > width - tile_width / 2 && y <= tile_height / 2) {
|
||||
p->c1 = clamp((int) (accumulation[TILE_BLOCK - 1][p->c1] * 255));
|
||||
continue;
|
||||
}
|
||||
// bottom-left corner
|
||||
if (x <= tile_width / 2 && y > height - tile_height / 2) {
|
||||
p->c1 = clamp((int) (accumulation[TILE_BLOCK * (TILE_BLOCK - 1)][p->c1] * 255));
|
||||
continue;
|
||||
}
|
||||
// bottom-right corner
|
||||
if (x > width - tile_width / 2 && y > height - tile_height / 2) {
|
||||
p->c1 = clamp((int) (accumulation[TILE_BLOCK * TILE_BLOCK - 1][p->c1] * 255));
|
||||
continue;
|
||||
}
|
||||
|
||||
int tile_x_index = (x - tile_width / 2) / tile_width;
|
||||
int tile_y_index = (y - tile_height / 2) / tile_height;
|
||||
int tile_index1 = tile_x_index + TILE_BLOCK * tile_y_index;
|
||||
int tile_index2 = tile_index1 + 1;
|
||||
float u = (float) (x - (tile_x_index * tile_width + tile_width / 2)) / tile_width;
|
||||
// top side and bottom side
|
||||
if (y <= tile_height / 2 || y > height - tile_height / 2) {
|
||||
p->c1 = clamp((int) (255 * (accumulation[tile_index1][p->c1] * (1 - u) +
|
||||
accumulation[tile_index2][p->c1] * u)));
|
||||
continue;
|
||||
}
|
||||
|
||||
int tile_index3 = tile_index1 + TILE_BLOCK;
|
||||
float v = (float) (y - (tile_y_index * tile_height + tile_height / 2)) / tile_height;
|
||||
// left side and right side
|
||||
if (x <= tile_width / 2 || x > width - tile_width / 2) {
|
||||
p->c1 = clamp((int) (255 * (accumulation[tile_index1][p->c1] * (1 - v) +
|
||||
accumulation[tile_index3][p->c1] * v)));
|
||||
continue;
|
||||
}
|
||||
|
||||
int tile_index4 = tile_index2 + TILE_BLOCK;
|
||||
p->c1 = clamp((int) (255 * (
|
||||
accumulation[tile_index1][p->c1] * (1 - u) * (1 - v)
|
||||
+ accumulation[tile_index2][p->c1] * u * (1 - v)
|
||||
+ accumulation[tile_index3][p->c1] * (1 - u) * v
|
||||
+ accumulation[tile_index4][p->c1] * u * v)));
|
||||
}
|
||||
}
|
||||
|
||||
if (to_gray) {
|
||||
for (int i = 0; i < width * height; ++i) {
|
||||
PIXEL *p = pixels + i;
|
||||
p->c2 = p->c1;
|
||||
p->c3 = p->c1;
|
||||
}
|
||||
} else {
|
||||
// YUVA to RGBA
|
||||
for (int i = 0; i < width * height; ++i) {
|
||||
PIXEL *p = pixels + i;
|
||||
int t1 = (int) (p->c1 + (p->c2 - 128) * -0.00093f + (p->c3 - 128) * 1.401687f);
|
||||
int t2 = (int) (p->c1 + (p->c2 - 128) * -0.3437f + (p->c3 - 128) * -0.71417f);
|
||||
int t3 = (int) (p->c1 + (p->c2 - 128) * 1.77216f + (p->c3 - 128) * 0.00099f);
|
||||
p->c1 = clamp(t1);
|
||||
p->c2 = clamp(t2);
|
||||
p->c3 = clamp(t3);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
# Copyright 2018 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.
|
||||
|
||||
cmake_minimum_required(VERSION 3.4.1)
|
||||
project(gif C)
|
||||
|
||||
set(GIFLIB_SOURCES
|
||||
giflib/dgif_lib.c
|
||||
giflib/gif_hash.c
|
||||
giflib/gifalloc.c
|
||||
giflib/openbsd-reallocarray.c
|
||||
)
|
||||
set(GIFLIB_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/giflib)
|
||||
|
||||
add_library(gif STATIC ${GIFLIB_SOURCES})
|
||||
target_include_directories(gif PUBLIC ${GIFLIB_INCLUDES})
|
||||
@@ -1,192 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 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.
|
||||
*/
|
||||
|
||||
//
|
||||
// Created by Hippo on 12/27/2015.
|
||||
//
|
||||
|
||||
#include <android/data_space.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "image.h"
|
||||
#include "image_utils.h"
|
||||
#include "filter/clahe.h"
|
||||
#include "filter/gray.h"
|
||||
#include "log.h"
|
||||
|
||||
void decodeCommon(IMAGE *image) {
|
||||
AImageDecoder *decoder = image->decoder;
|
||||
AImageDecoder_setDataSpace(decoder, ADATASPACE_SRGB);
|
||||
AImageDecoder_setAndroidBitmapFormat(decoder, ANDROID_BITMAP_FORMAT_RGBA_8888);
|
||||
const AImageDecoderHeaderInfo *headerInfo = AImageDecoder_getHeaderInfo(decoder);
|
||||
image->height = AImageDecoderHeaderInfo_getHeight(headerInfo);
|
||||
image->width = AImageDecoderHeaderInfo_getWidth(headerInfo);
|
||||
image->alpha =
|
||||
AImageDecoderHeaderInfo_getAlphaFlags(headerInfo) == ANDROID_BITMAP_FLAGS_ALPHA_PREMUL;
|
||||
image->stride = AImageDecoder_getMinimumStride(decoder);
|
||||
image->bufferLen = image->height * image->stride;
|
||||
image->buffer = malloc(image->bufferLen);
|
||||
if (image->isAnimated) {
|
||||
AImageDecoder_setInternallyHandleDisposePrevious(decoder, false);
|
||||
image->frameInfo = AImageDecoderFrameInfo_create();
|
||||
AImageDecoder_getFrameInfo(decoder, image->frameInfo);
|
||||
}
|
||||
AImageDecoder_decodeImage(decoder, image->buffer, image->stride, image->bufferLen);
|
||||
}
|
||||
|
||||
IMAGE *createFromFd(JNIEnv *env, int fd, bool partially, int *format) {
|
||||
AImageDecoder *decoder;
|
||||
int r = AImageDecoder_createFromFd(fd, &decoder);
|
||||
LOGD("%s%d", "Create ImageDecoder with ret ", r);
|
||||
if (r)
|
||||
return NULL;
|
||||
IMAGE *image = calloc(1, sizeof(IMAGE));
|
||||
image->isAnimated = *format = AImageDecoder_isAnimated(decoder);
|
||||
image->decoder = decoder;
|
||||
decodeCommon(image);
|
||||
return image;
|
||||
}
|
||||
|
||||
IMAGE *createFromAddr(JNIEnv *env, void *addr, long size, bool partially, int *format) {
|
||||
AImageDecoder *decoder;
|
||||
int r = AImageDecoder_createFromBuffer((const void *) addr, size, &decoder);
|
||||
LOGD("%s%d", "Create ImageDecoder with ret ", r);
|
||||
if (r)
|
||||
return NULL;
|
||||
IMAGE *image = calloc(1, sizeof(IMAGE));
|
||||
image->isAnimated = *format = AImageDecoder_isAnimated(decoder);
|
||||
image->decoder = decoder;
|
||||
image->srcBuffer = addr;
|
||||
decodeCommon(image);
|
||||
return image;
|
||||
}
|
||||
|
||||
void *create(int32_t width, int32_t height, const void *data) {
|
||||
IMAGE *plain = NULL;
|
||||
void *buffer = NULL;
|
||||
size_t length;
|
||||
|
||||
plain = malloc(sizeof(IMAGE));
|
||||
if (plain == NULL) {
|
||||
WTF_OM;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
length = (size_t) (width * height * 4);
|
||||
|
||||
buffer = malloc(length);
|
||||
if (buffer == NULL) {
|
||||
WTF_OM;
|
||||
free(plain);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(buffer, data, length);
|
||||
|
||||
plain->width = width;
|
||||
plain->height = height;
|
||||
plain->buffer = buffer;
|
||||
plain->bufferLen = length;
|
||||
plain->decoder = NULL;
|
||||
|
||||
return plain;
|
||||
}
|
||||
|
||||
int get_width(IMAGE *image, int format) {
|
||||
return image->width;
|
||||
}
|
||||
|
||||
int get_height(IMAGE *image, int format) {
|
||||
return image->height;
|
||||
}
|
||||
|
||||
int get_byte_count(IMAGE *image, int format) {
|
||||
return image->bufferLen;
|
||||
}
|
||||
|
||||
void render(IMAGE *image, int format, int src_x, int src_y,
|
||||
void *dst, int dst_w, int dst_h, int dst_x, int dst_y,
|
||||
int width, int height, bool fill_blank, int default_color) {
|
||||
copy_pixels(image->buffer, image->width, image->height, src_x, src_y, dst, dst_w, dst_h, dst_x,
|
||||
dst_y, width, height, fill_blank, default_color);
|
||||
}
|
||||
|
||||
void advance(IMAGE *image, int format) {
|
||||
if (format) {
|
||||
AImageDecoder_advanceFrame(image->decoder);
|
||||
if (AImageDecoder_decodeImage(image->decoder, image->buffer, image->stride,
|
||||
image->bufferLen) == ANDROID_IMAGE_DECODER_FINISHED) {
|
||||
AImageDecoder_rewind(image->decoder);
|
||||
AImageDecoder_decodeImage(image->decoder, image->buffer, image->stride,
|
||||
image->bufferLen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int get_delay(IMAGE *image, int format) {
|
||||
if (format)
|
||||
return AImageDecoderFrameInfo_getDuration(image->frameInfo) / 1000000;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool is_opaque(IMAGE *image, int format) {
|
||||
return image->alpha;
|
||||
}
|
||||
|
||||
static void get_image_data(IMAGE *image, int format, void **pixel, int *width, int *height) {
|
||||
*pixel = image->buffer;
|
||||
*width = image->width;
|
||||
*height = image->height;
|
||||
}
|
||||
|
||||
bool is_gray(IMAGE *image, int format, int error) {
|
||||
void *pixel = NULL;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
get_image_data(image, format, &pixel, &width, &height);
|
||||
|
||||
if (pixel == NULL || width == 0 || height == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return IMAGE_is_gray(pixel, width, height, error);
|
||||
}
|
||||
|
||||
void clahe(IMAGE *image, int format, bool to_gray) {
|
||||
void *pixel = NULL;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
get_image_data(image, format, &pixel, &width, &height);
|
||||
|
||||
if (pixel == NULL || width == 0 || height == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
IMAGE_clahe(pixel, width, height, to_gray);
|
||||
}
|
||||
|
||||
void recycle(JNIEnv *env, IMAGE *image, int format) {
|
||||
free(image->buffer);
|
||||
image->buffer = NULL;
|
||||
AImageDecoderFrameInfo_delete(image->frameInfo);
|
||||
AImageDecoder_delete(image->decoder);
|
||||
free(image->srcBuffer);
|
||||
free(image);
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 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.
|
||||
*/
|
||||
|
||||
//
|
||||
// Created by Hippo on 12/27/2015.
|
||||
//
|
||||
|
||||
#ifndef IMAGE_IMAGE_H
|
||||
#define IMAGE_IMAGE_H
|
||||
|
||||
#include <android/imagedecoder.h>
|
||||
#include <stdbool.h>
|
||||
#include <jni.h>
|
||||
|
||||
typedef struct {
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
void *buffer;
|
||||
int bufferLen;
|
||||
AImageDecoder *decoder;
|
||||
void *srcBuffer;
|
||||
size_t stride;
|
||||
bool isAnimated;
|
||||
AImageDecoderFrameInfo *frameInfo;
|
||||
bool alpha;
|
||||
} IMAGE;
|
||||
|
||||
IMAGE *createFromFd(JNIEnv *env, int fd, bool partially, int *format);
|
||||
|
||||
IMAGE *createFromAddr(JNIEnv *env, void *addr, long size, bool partially, int *format);
|
||||
|
||||
void *create(int32_t width, int32_t height, const void *data);
|
||||
|
||||
int get_width(IMAGE *image, int format);
|
||||
|
||||
int get_height(IMAGE *image, int format);
|
||||
|
||||
int get_byte_count(IMAGE *image, int format);
|
||||
|
||||
void render(IMAGE *image, int format, int src_x, int src_y,
|
||||
void *dst, int dst_w, int dst_h, int dst_x, int dst_y,
|
||||
int width, int height, bool fill_blank, int default_color);
|
||||
|
||||
void advance(IMAGE *image, int format);
|
||||
|
||||
int get_delay(IMAGE *image, int format);
|
||||
|
||||
bool is_opaque(IMAGE *image, int format);
|
||||
|
||||
bool is_gray(IMAGE *image, int format, int error);
|
||||
|
||||
void clahe(IMAGE *image, int format, bool to_gray);
|
||||
|
||||
void recycle(JNIEnv *env, IMAGE *image, int format);
|
||||
|
||||
#endif //IMAGE_IMAGE_H
|
||||
@@ -0,0 +1,60 @@
|
||||
# Copyright 2015 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.
|
||||
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
SUPPORT_FORMAT := plain jpeg png gif
|
||||
|
||||
LOCAL_MODULE := image
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/../stream
|
||||
LOCAL_SRC_FILES := \
|
||||
image.c \
|
||||
image_utils.c \
|
||||
java_wrapper.c
|
||||
LOCAL_LDLIBS := -llog -ljnigraphics -lGLESv2
|
||||
LOCAL_STATIC_LIBRARIES := stream
|
||||
|
||||
ifeq ($(filter plain, $(SUPPORT_FORMAT)), plain)
|
||||
LOCAL_SRC_FILES += image_plain.c
|
||||
else
|
||||
LOCAL_CFLAGS += -DIMAGE_NOT_SUPPORT_PLAIN
|
||||
endif
|
||||
|
||||
ifeq ($(filter jpeg, $(SUPPORT_FORMAT)), jpeg)
|
||||
LOCAL_C_INCLUDES += $(LOCAL_PATH)/../libjpeg-turbo
|
||||
LOCAL_SRC_FILES += image_jpeg.c
|
||||
LOCAL_STATIC_LIBRARIES += jpeg-turbo
|
||||
else
|
||||
LOCAL_CFLAGS += -DIMAGE_NOT_SUPPORT_JPEG
|
||||
endif
|
||||
|
||||
ifeq ($(filter png, $(SUPPORT_FORMAT)), png)
|
||||
LOCAL_C_INCLUDES += $(LOCAL_PATH)/../libpng
|
||||
LOCAL_SRC_FILES += image_png.c
|
||||
LOCAL_STATIC_LIBRARIES += png
|
||||
else
|
||||
LOCAL_CFLAGS += -DIMAGE_NOT_SUPPORT_PNG
|
||||
endif
|
||||
|
||||
ifeq ($(filter gif, $(SUPPORT_FORMAT)), gif)
|
||||
LOCAL_C_INCLUDES += $(LOCAL_PATH)/../giflib
|
||||
LOCAL_SRC_FILES += image_gif.c
|
||||
LOCAL_STATIC_LIBRARIES += gif
|
||||
else
|
||||
LOCAL_CFLAGS += -DIMAGE_NOT_SUPPORT_GIF
|
||||
endif
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
@@ -0,0 +1,44 @@
|
||||
# Copyright 2018 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.
|
||||
|
||||
cmake_minimum_required(VERSION 3.4.1)
|
||||
project(image C)
|
||||
|
||||
set(IMAGE_SOURCES
|
||||
image.c
|
||||
image_utils.c
|
||||
image_plain.c
|
||||
image_jpeg.c
|
||||
image_png.c
|
||||
image_gif.c
|
||||
filter/clahe.c
|
||||
filter/gray.c
|
||||
java_wrapper.c
|
||||
)
|
||||
set(IMAGE_INCLUDES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
set(IMAGE_LIBRARIES
|
||||
log
|
||||
jnigraphics
|
||||
GLESv2
|
||||
stream
|
||||
jpeg
|
||||
png
|
||||
gif
|
||||
)
|
||||
|
||||
add_library(image STATIC ${IMAGE_SOURCES})
|
||||
target_include_directories(image PRIVATE ${IMAGE_INCLUDES})
|
||||
target_link_libraries(image PRIVATE ${IMAGE_LIBRARIES})
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2015 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.
|
||||
*/
|
||||
|
||||
//
|
||||
// Created by Hippo on 12/27/2015.
|
||||
//
|
||||
|
||||
#ifndef IMAGE_CONFIG_H
|
||||
#define IMAGE_CONFIG_H
|
||||
|
||||
#ifndef IMAGE_NOT_SUPPORT_PLAIN
|
||||
#define IMAGE_SUPPORT_PLAIN
|
||||
#endif
|
||||
#ifndef IMAGE_NOT_SUPPORT_JPEG
|
||||
#define IMAGE_SUPPORT_JPEG
|
||||
#endif
|
||||
#ifndef IMAGE_NOT_SUPPORT_PNG
|
||||
#define IMAGE_SUPPORT_PNG
|
||||
#endif
|
||||
#ifndef IMAGE_NOT_SUPPORT_GIF
|
||||
#define IMAGE_SUPPORT_GIF
|
||||
#endif
|
||||
|
||||
#endif //IMAGE_CONFIG_H
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright 2018 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.
|
||||
*/
|
||||
|
||||
#include "clahe.h"
|
||||
#include "pixel.h"
|
||||
#include "../../utils.h"
|
||||
#include "../../log.h"
|
||||
|
||||
#define TILE_BLOCK 8
|
||||
#define LIMIT_MULTIPLE 4
|
||||
#define CL_MAX_LOOP_NUMBER 4
|
||||
|
||||
static BYTE clamp(int t) {
|
||||
t = t > 255 ? 255 : t;
|
||||
t = t < 0 ? 0 : t;
|
||||
return (BYTE) t;
|
||||
}
|
||||
|
||||
void IMAGE_clahe(void* data, int width, int height, bool to_gray) {
|
||||
if (width < TILE_BLOCK || height < TILE_BLOCK) {
|
||||
return;
|
||||
}
|
||||
|
||||
PIXEL* pixels = data;
|
||||
|
||||
if (to_gray) {
|
||||
for (int i = 0; i < width * height; ++i) {
|
||||
PIXEL* p = pixels + i;
|
||||
int t1 = (int) (p->c1 * 0.299f + p->c2 * 0.587f + p->c3 * 0.114f);
|
||||
p->c1 = clamp(t1);
|
||||
}
|
||||
} else {
|
||||
// RGBA to YUVA
|
||||
for (int i = 0; i < width * height; ++i) {
|
||||
PIXEL* p = pixels + i;
|
||||
int t1 = (int) (p->c1 * 0.299f + p->c2 * 0.587f + p->c3 * 0.114f);
|
||||
int t2 = (int) (p->c1 * -0.169f + p->c2 * -0.331f + p->c3 * 0.5f + 128);
|
||||
int t3 = (int) (p->c1 * 0.5f + p->c2 * -0.419f + p->c3 * -0.081f + 128);
|
||||
p->c1 = clamp(t1);
|
||||
p->c2 = clamp(t2);
|
||||
p->c3 = clamp(t3);
|
||||
}
|
||||
}
|
||||
|
||||
int tile_width = width / TILE_BLOCK;
|
||||
int tile_height = height / TILE_BLOCK;
|
||||
int histogram[TILE_BLOCK * TILE_BLOCK][256] = {0};
|
||||
float accumulation[TILE_BLOCK * TILE_BLOCK][256] = {0.0f};
|
||||
|
||||
// Build histogram and accumulation
|
||||
for (int i = 0; i < TILE_BLOCK; i++) {
|
||||
for (int j = 0; j < TILE_BLOCK; j++) {
|
||||
int x_start = tile_width * i;
|
||||
int x_end = i == TILE_BLOCK - 1 ? width : x_start + tile_width;
|
||||
int y_start = tile_height * j;
|
||||
int y_end = j == TILE_BLOCK - 1 ? height : y_start + tile_height;
|
||||
int tile_size = (x_end - x_start) * (y_end - y_start);
|
||||
int tile_index = i + TILE_BLOCK * j;
|
||||
|
||||
// Count
|
||||
for(int x = x_start; x < x_end; x++) {
|
||||
for(int y = y_start; y < y_end ; y++) {
|
||||
PIXEL* p = pixels + x + y * width;
|
||||
histogram[tile_index][p->c1] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// CL
|
||||
int limit = tile_size * LIMIT_MULTIPLE / 256;
|
||||
for (int m = 0; m < CL_MAX_LOOP_NUMBER; m++) {
|
||||
int steal = 0;
|
||||
for (int n = 0; n < 256; n++) {
|
||||
if (histogram[tile_index][n] > limit) {
|
||||
steal += histogram[tile_index][n] - limit;
|
||||
histogram[tile_index][n] = limit;
|
||||
}
|
||||
}
|
||||
int bonus = steal / 256;
|
||||
if (bonus == 0 && steal != 0 && m == 0) {
|
||||
bonus = 1;
|
||||
}
|
||||
if (bonus == 0) {
|
||||
break;
|
||||
}
|
||||
for (int n = 0; n < 256; n++) {
|
||||
histogram[tile_index][n] += bonus;
|
||||
}
|
||||
}
|
||||
|
||||
// accumulation
|
||||
for (int n = 0; n < 256; n++) {
|
||||
accumulation[tile_index][n] = (float) histogram[tile_index][n] / tile_size;
|
||||
if (n != 0) {
|
||||
accumulation[tile_index][n] += accumulation[tile_index][n - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Interpolation
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (int y = 0; y < height; y++) {
|
||||
PIXEL* p = pixels + x + y * width;
|
||||
// top-left corner
|
||||
if (x <= tile_width / 2 && y <= tile_height / 2) {
|
||||
p->c1 = clamp((int) (accumulation[0][p->c1] * 255));
|
||||
continue;
|
||||
}
|
||||
// top-right corner
|
||||
if (x > width - tile_width / 2 && y <= tile_height / 2) {
|
||||
p->c1 = clamp((int) (accumulation[TILE_BLOCK - 1][p->c1] * 255));
|
||||
continue;
|
||||
}
|
||||
// bottom-left corner
|
||||
if (x <= tile_width / 2 && y > height - tile_height / 2) {
|
||||
p->c1 = clamp((int) (accumulation[TILE_BLOCK * (TILE_BLOCK - 1)][p->c1] * 255));
|
||||
continue;
|
||||
}
|
||||
// bottom-right corner
|
||||
if (x > width - tile_width / 2 && y > height - tile_height / 2) {
|
||||
p->c1 = clamp((int) (accumulation[TILE_BLOCK * TILE_BLOCK - 1][p->c1] * 255));
|
||||
continue;
|
||||
}
|
||||
|
||||
int tile_x_index = (x - tile_width / 2) / tile_width;
|
||||
int tile_y_index = (y - tile_height / 2) / tile_height;
|
||||
int tile_index1 = tile_x_index + TILE_BLOCK * tile_y_index;
|
||||
int tile_index2 = tile_index1 + 1;
|
||||
float u = (float) (x - (tile_x_index * tile_width + tile_width / 2)) / tile_width;
|
||||
// top side and bottom side
|
||||
if (y <= tile_height / 2 || y > height - tile_height / 2) {
|
||||
p->c1 = clamp((int) (255 * (accumulation[tile_index1][p->c1] * (1 - u) + accumulation[tile_index2][p->c1] * u)));
|
||||
continue;
|
||||
}
|
||||
|
||||
int tile_index3 = tile_index1 + TILE_BLOCK;
|
||||
float v = (float) (y - (tile_y_index * tile_height + tile_height / 2)) / tile_height;
|
||||
// left side and right side
|
||||
if (x <= tile_width / 2 || x > width - tile_width / 2) {
|
||||
p->c1 = clamp((int) (255 * (accumulation[tile_index1][p->c1] * (1 - v) + accumulation[tile_index3][p->c1] * v)));
|
||||
continue;
|
||||
}
|
||||
|
||||
int tile_index4 = tile_index2 + TILE_BLOCK;
|
||||
p->c1 = clamp((int) (255 * (
|
||||
accumulation[tile_index1][p->c1] * (1 - u) * (1 - v)
|
||||
+ accumulation[tile_index2][p->c1] * u * (1 - v)
|
||||
+ accumulation[tile_index3][p->c1] * (1 - u) * v
|
||||
+ accumulation[tile_index4][p->c1] * u * v)));
|
||||
}
|
||||
}
|
||||
|
||||
if (to_gray) {
|
||||
for (int i = 0; i < width * height; ++i) {
|
||||
PIXEL* p = pixels + i;
|
||||
p->c2 = p->c1;
|
||||
p->c3 = p->c1;
|
||||
}
|
||||
} else {
|
||||
// YUVA to RGBA
|
||||
for (int i = 0; i < width * height; ++i) {
|
||||
PIXEL* p = pixels + i;
|
||||
int t1 = (int) (p->c1 + (p->c2 - 128) * -0.00093f + (p->c3 - 128) * 1.401687f);
|
||||
int t2 = (int) (p->c1 + (p->c2 - 128) * -0.3437f + (p->c3 - 128) * -0.71417f);
|
||||
int t3 = (int) (p->c1 + (p->c2 - 128) * 1.77216f + (p->c3 - 128) * 0.00099f);
|
||||
p->c1 = clamp(t1);
|
||||
p->c2 = clamp(t2);
|
||||
p->c3 = clamp(t3);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,6 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
void IMAGE_clahe(void *data, int width, int height, bool to_gray);
|
||||
void IMAGE_clahe(void* data, int width, int height, bool to_gray);
|
||||
|
||||
#endif //IMAGE_CLAHE_H
|
||||
@@ -17,20 +17,20 @@
|
||||
#include "gray.h"
|
||||
#include "pixel.h"
|
||||
|
||||
bool IMAGE_is_gray(void *data, int width, int height, int error) {
|
||||
PIXEL *p = data;
|
||||
bool IMAGE_is_gray(void* data, int width, int height, int error) {
|
||||
PIXEL* p = data;
|
||||
|
||||
for (int i = 0, n = width * height; i < n; i++, p++) {
|
||||
int e1 = p->c1 - p->c2;
|
||||
int e2 = p->c1 - p->c3;
|
||||
int e3 = p->c2 - p->c3;
|
||||
for (int i = 0, n = width * height; i < n; i++, p++) {
|
||||
int e1 = p->c1 - p->c2;
|
||||
int e2 = p->c1 - p->c3;
|
||||
int e3 = p->c2 - p->c3;
|
||||
|
||||
if (e1 > error || e1 < -error
|
||||
|| e2 > error || e2 < -error
|
||||
|| e3 > error || e3 < -error) {
|
||||
return false;
|
||||
}
|
||||
if (e1 > error || e1 < -error
|
||||
|| e2 > error || e2 < -error
|
||||
|| e3 > error || e3 < -error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
@@ -19,6 +19,6 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
bool IMAGE_is_gray(void *data, int width, int height, int error);
|
||||
bool IMAGE_is_gray(void* data, int width, int height, int error);
|
||||
|
||||
#endif //IMAGE_GRAY_H
|
||||
@@ -19,11 +19,12 @@
|
||||
|
||||
typedef unsigned char BYTE;
|
||||
|
||||
typedef struct {
|
||||
BYTE c1;
|
||||
BYTE c2;
|
||||
BYTE c3;
|
||||
BYTE c4;
|
||||
typedef struct
|
||||
{
|
||||
BYTE c1;
|
||||
BYTE c2;
|
||||
BYTE c3;
|
||||
BYTE c4;
|
||||
} PIXEL;
|
||||
|
||||
#endif //IMAGE_PIXEL_H
|
||||
@@ -0,0 +1,532 @@
|
||||
/*
|
||||
* Copyright 2015 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.
|
||||
*/
|
||||
|
||||
//
|
||||
// Created by Hippo on 12/27/2015.
|
||||
//
|
||||
|
||||
#include "image.h"
|
||||
#include "image_plain.h"
|
||||
#include "image_jpeg.h"
|
||||
#include "image_png.h"
|
||||
#include "image_gif.h"
|
||||
#include "filter/clahe.h"
|
||||
#include "filter/gray.h"
|
||||
#include "../log.h"
|
||||
|
||||
static int get_format(JNIEnv* env, InputStream* stream)
|
||||
{
|
||||
unsigned char temp[2];
|
||||
int read = read_input_stream(env, stream, temp, 0, 2);
|
||||
|
||||
if (read == 2) {
|
||||
#ifdef IMAGE_SUPPORT_JPEG
|
||||
if (temp[0] == IMAGE_JPEG_MAGIC_NUMBER_0 && temp[1] == IMAGE_JPEG_MAGIC_NUMBER_1) {
|
||||
return IMAGE_FORMAT_JPEG;
|
||||
}
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_PNG
|
||||
if (temp[0] == IMAGE_PNG_MAGIC_NUMBER_0 && temp[1] == IMAGE_PNG_MAGIC_NUMBER_1) {
|
||||
return IMAGE_FORMAT_PNG;
|
||||
}
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_GIF
|
||||
if (temp[0] == IMAGE_GIF_MAGIC_NUMBER_0 && temp[1] == IMAGE_GIF_MAGIC_NUMBER_1) {
|
||||
return IMAGE_FORMAT_GIF;
|
||||
}
|
||||
#endif
|
||||
LOGE(MSG("Can't recognize the two magic number: %d, %d"), temp[0], temp[1]);
|
||||
} else {
|
||||
LOGE(MSG("Can't read two magic number from stream"));
|
||||
}
|
||||
|
||||
return IMAGE_FORMAT_UNKNOWN;
|
||||
}
|
||||
|
||||
void* decode(JNIEnv* env, InputStream* stream, bool partially, int* format)
|
||||
{
|
||||
unsigned char magic_numbers[2];
|
||||
PatchHeadInputStream* patch_head_input_stream;
|
||||
|
||||
*format = get_format(env, stream);
|
||||
|
||||
switch (*format) {
|
||||
#ifdef IMAGE_SUPPORT_JPEG
|
||||
case IMAGE_FORMAT_JPEG:
|
||||
magic_numbers[0] = IMAGE_JPEG_MAGIC_NUMBER_0;
|
||||
magic_numbers[1] = IMAGE_JPEG_MAGIC_NUMBER_1;
|
||||
break;
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_PNG
|
||||
case IMAGE_FORMAT_PNG:
|
||||
magic_numbers[0] = IMAGE_PNG_MAGIC_NUMBER_0;
|
||||
magic_numbers[1] = IMAGE_PNG_MAGIC_NUMBER_1;
|
||||
break;
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_GIF
|
||||
case IMAGE_FORMAT_GIF:
|
||||
magic_numbers[0] = IMAGE_GIF_MAGIC_NUMBER_0;
|
||||
magic_numbers[1] = IMAGE_GIF_MAGIC_NUMBER_1;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
LOGE(MSG("Can't detect format %d"), *format);
|
||||
destroy_input_stream(env, &stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
patch_head_input_stream = create_patch_head_input_stream(stream, magic_numbers, 2);
|
||||
if (patch_head_input_stream == NULL){
|
||||
WTF_OM;
|
||||
destroy_input_stream(env, &stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch (*format) {
|
||||
#ifdef IMAGE_SUPPORT_JPEG
|
||||
case IMAGE_FORMAT_JPEG:
|
||||
return JPEG_decode(env, patch_head_input_stream, partially);
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_PNG
|
||||
case IMAGE_FORMAT_PNG:
|
||||
return PNG_decode(env, patch_head_input_stream, partially);
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_GIF
|
||||
case IMAGE_FORMAT_GIF:
|
||||
return GIF_decode(env, patch_head_input_stream, partially);
|
||||
#endif
|
||||
default:
|
||||
LOGE(MSG("Can't detect format %d"), *format);
|
||||
close_patch_head_input_stream(env, patch_head_input_stream);
|
||||
destroy_patch_head_input_stream(env, &patch_head_input_stream);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void* create(unsigned int width, unsigned int height, const void* data)
|
||||
{
|
||||
#ifdef IMAGE_SUPPORT_PLAIN
|
||||
return PLAIN_create(width, height, data);
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool complete(JNIEnv* env, void* image, int format)
|
||||
{
|
||||
switch (format) {
|
||||
#ifdef IMAGE_SUPPORT_PLAIN
|
||||
case IMAGE_FORMAT_PLAIN:
|
||||
return PLAIN_complete((PLAIN*) image);
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_JPEG
|
||||
case IMAGE_FORMAT_JPEG:
|
||||
return JPEG_complete((JPEG*) image);
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_PNG
|
||||
case IMAGE_FORMAT_PNG:
|
||||
return PNG_complete(env, (PNG*) image);
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_GIF
|
||||
case IMAGE_FORMAT_GIF:
|
||||
return GIF_complete(env, (GIF*) image);
|
||||
#endif
|
||||
default:
|
||||
LOGE(MSG("Can't detect format %d"), format);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_completed(void* image, int format)
|
||||
{
|
||||
switch (format) {
|
||||
#ifdef IMAGE_SUPPORT_PLAIN
|
||||
case IMAGE_FORMAT_PLAIN:
|
||||
return PLAIN_is_completed((PLAIN*) image);
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_JPEG
|
||||
case IMAGE_FORMAT_JPEG:
|
||||
return JPEG_is_completed((JPEG*) image);
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_PNG
|
||||
case IMAGE_FORMAT_PNG:
|
||||
return PNG_is_completed((PNG*) image);
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_GIF
|
||||
case IMAGE_FORMAT_GIF:
|
||||
return GIF_is_completed((GIF*) image);
|
||||
#endif
|
||||
default:
|
||||
LOGE(MSG("Can't detect format %d"), format);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int get_width(void* image, int format)
|
||||
{
|
||||
switch (format) {
|
||||
#ifdef IMAGE_SUPPORT_PLAIN
|
||||
case IMAGE_FORMAT_PLAIN:
|
||||
return PLAIN_get_width((PLAIN*) image);
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_JPEG
|
||||
case IMAGE_FORMAT_JPEG:
|
||||
return JPEG_get_width((JPEG*) image);
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_PNG
|
||||
case IMAGE_FORMAT_PNG:
|
||||
return PNG_get_width((PNG*) image);
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_GIF
|
||||
case IMAGE_FORMAT_GIF:
|
||||
return GIF_get_width((GIF*) image);
|
||||
#endif
|
||||
default:
|
||||
LOGE(MSG("Can't detect format %d"), format);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int get_height(void* image, int format)
|
||||
{
|
||||
switch (format) {
|
||||
#ifdef IMAGE_SUPPORT_PLAIN
|
||||
case IMAGE_FORMAT_PLAIN:
|
||||
return PLAIN_get_height((PLAIN*) image);
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_JPEG
|
||||
case IMAGE_FORMAT_JPEG:
|
||||
return JPEG_get_height((JPEG*) image);
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_PNG
|
||||
case IMAGE_FORMAT_PNG:
|
||||
return PNG_get_height((PNG*) image);
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_GIF
|
||||
case IMAGE_FORMAT_GIF:
|
||||
return GIF_get_height((GIF*) image);
|
||||
#endif
|
||||
default:
|
||||
LOGE(MSG("Can't detect format %d"), format);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int get_byte_count(void* image, int format)
|
||||
{
|
||||
switch (format) {
|
||||
#ifdef IMAGE_SUPPORT_PLAIN
|
||||
case IMAGE_FORMAT_PLAIN:
|
||||
return PLAIN_get_byte_count((PLAIN*) image);
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_JPEG
|
||||
case IMAGE_FORMAT_JPEG:
|
||||
return JPEG_get_byte_count((JPEG*) image);
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_PNG
|
||||
case IMAGE_FORMAT_PNG:
|
||||
return PNG_get_byte_count((PNG*) image);
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_GIF
|
||||
case IMAGE_FORMAT_GIF:
|
||||
return GIF_get_byte_count((GIF*) image);
|
||||
#endif
|
||||
default:
|
||||
LOGE(MSG("Can't detect format %d"), format);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void render(void* image, int format, int src_x, int src_y,
|
||||
void* dst, int dst_w, int dst_h, int dst_x, int dst_y,
|
||||
int width, int height, bool fill_blank, int default_color)
|
||||
{
|
||||
switch (format) {
|
||||
#ifdef IMAGE_SUPPORT_PLAIN
|
||||
case IMAGE_FORMAT_PLAIN:
|
||||
PLAIN_render((PLAIN*) image, src_x, src_y,
|
||||
dst, dst_w, dst_h, dst_x, dst_y,
|
||||
width, height, fill_blank, default_color);
|
||||
break;
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_JPEG
|
||||
case IMAGE_FORMAT_JPEG:
|
||||
JPEG_render((JPEG*) image, src_x, src_y,
|
||||
dst, dst_w, dst_h, dst_x, dst_y,
|
||||
width, height, fill_blank, default_color);
|
||||
break;
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_PNG
|
||||
case IMAGE_FORMAT_PNG:
|
||||
PNG_render((PNG*) image, src_x, src_y,
|
||||
dst, dst_w, dst_h, dst_x, dst_y,
|
||||
width, height, fill_blank, default_color);
|
||||
break;
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_GIF
|
||||
case IMAGE_FORMAT_GIF:
|
||||
GIF_render((GIF*) image, src_x, src_y,
|
||||
dst, dst_w, dst_h, dst_x, dst_y,
|
||||
width, height, fill_blank, default_color);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
LOGE(MSG("Can't detect format %d"), format);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void advance(void* image, int format)
|
||||
{
|
||||
switch (format) {
|
||||
#ifdef IMAGE_SUPPORT_PLAIN
|
||||
case IMAGE_FORMAT_PLAIN:
|
||||
PLAIN_advance((PLAIN*) image);
|
||||
break;
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_JPEG
|
||||
case IMAGE_FORMAT_JPEG:
|
||||
JPEG_advance((JPEG*) image);
|
||||
break;
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_PNG
|
||||
case IMAGE_FORMAT_PNG:
|
||||
PNG_advance((PNG*) image);
|
||||
break;
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_GIF
|
||||
case IMAGE_FORMAT_GIF:
|
||||
GIF_advance((GIF*) image);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
LOGE(MSG("Can't detect format %d"), format);
|
||||
}
|
||||
}
|
||||
|
||||
int get_delay(void* image, int format)
|
||||
{
|
||||
switch (format) {
|
||||
#ifdef IMAGE_SUPPORT_PLAIN
|
||||
case IMAGE_FORMAT_PLAIN:
|
||||
return PLAIN_get_delay((PLAIN*) image);
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_JPEG
|
||||
case IMAGE_FORMAT_JPEG:
|
||||
return JPEG_get_delay((JPEG*) image);
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_PNG
|
||||
case IMAGE_FORMAT_PNG:
|
||||
return PNG_get_delay((PNG*) image);
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_GIF
|
||||
case IMAGE_FORMAT_GIF:
|
||||
return GIF_get_delay((GIF*) image);
|
||||
#endif
|
||||
default:
|
||||
LOGE(MSG("Can't detect format %d"), format);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int get_frame_count(void* image, int format)
|
||||
{
|
||||
switch (format) {
|
||||
#ifdef IMAGE_SUPPORT_PLAIN
|
||||
case IMAGE_FORMAT_PLAIN:
|
||||
return PLAIN_get_frame_count((PLAIN*) image);
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_JPEG
|
||||
case IMAGE_FORMAT_JPEG:
|
||||
return JPEG_get_frame_count((JPEG*) image);
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_PNG
|
||||
case IMAGE_FORMAT_PNG:
|
||||
return PNG_get_frame_count((PNG*) image);
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_GIF
|
||||
case IMAGE_FORMAT_GIF:
|
||||
return GIF_get_frame_count((GIF*) image);
|
||||
#endif
|
||||
default:
|
||||
LOGE(MSG("Can't detect format %d"), format);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_opaque(void* image, int format)
|
||||
{
|
||||
switch (format) {
|
||||
#ifdef IMAGE_SUPPORT_PLAIN
|
||||
case IMAGE_FORMAT_PLAIN:
|
||||
return PLAIN_is_opaque((PLAIN*) image);
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_JPEG
|
||||
case IMAGE_FORMAT_JPEG:
|
||||
return JPEG_is_opaque((JPEG*) image);
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_PNG
|
||||
case IMAGE_FORMAT_PNG:
|
||||
return PNG_is_opaque((PNG*) image);
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_GIF
|
||||
case IMAGE_FORMAT_GIF:
|
||||
return GIF_is_opaque((GIF*) image);
|
||||
#endif
|
||||
default:
|
||||
LOGE(MSG("Can't detect format %d"), format);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void get_image_data(void* image, int format, void** pixel, int* width, int* height) {
|
||||
switch (format) {
|
||||
#ifdef IMAGE_SUPPORT_PLAIN
|
||||
case IMAGE_FORMAT_PLAIN: {
|
||||
PLAIN* plain = (PLAIN*) image;
|
||||
*pixel = PLAIN_get_pixels(plain);
|
||||
*width = PLAIN_get_width(plain);
|
||||
*height = PLAIN_get_height(plain);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_JPEG
|
||||
case IMAGE_FORMAT_JPEG: {
|
||||
JPEG* jpeg = (JPEG*) image;
|
||||
*pixel = JPEG_get_pixels(jpeg);
|
||||
*width = JPEG_get_width(jpeg);
|
||||
*height = JPEG_get_height(jpeg);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_PNG
|
||||
case IMAGE_FORMAT_PNG: {
|
||||
PNG* png = (PNG*) image;
|
||||
*pixel = PNG_get_pixels(png);
|
||||
*width = PNG_get_width(png);
|
||||
*height = PNG_get_height(png);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_GIF
|
||||
case IMAGE_FORMAT_GIF: {
|
||||
GIF* gif = (GIF*) image;
|
||||
*pixel = GIF_get_pixels(gif);
|
||||
*width = GIF_get_width(gif);
|
||||
*height = GIF_get_height(gif);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
*pixel = NULL;
|
||||
*width = 0;
|
||||
*height = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_gray(void* image, int format, int error)
|
||||
{
|
||||
void* pixel = NULL;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
get_image_data(image, format, &pixel, &width, &height);
|
||||
|
||||
if (pixel == NULL || width == 0 || height == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return IMAGE_is_gray(pixel, width, height, error);
|
||||
}
|
||||
|
||||
void clahe(void* image, int format, bool to_gray)
|
||||
{
|
||||
void* pixel = NULL;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
get_image_data(image, format, &pixel, &width, &height);
|
||||
|
||||
if (pixel == NULL || width == 0 || height == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
IMAGE_clahe(pixel, width, height, to_gray);
|
||||
}
|
||||
|
||||
void recycle(JNIEnv *env, void* image, int format)
|
||||
{
|
||||
switch (format) {
|
||||
#ifdef IMAGE_SUPPORT_PLAIN
|
||||
case IMAGE_FORMAT_PLAIN:
|
||||
PLAIN_recycle((PLAIN*) image);
|
||||
break;
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_JPEG
|
||||
case IMAGE_FORMAT_JPEG:
|
||||
JPEG_recycle((JPEG*) image);
|
||||
break;
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_PNG
|
||||
case IMAGE_FORMAT_PNG:
|
||||
PNG_recycle(env, (PNG*) image);
|
||||
break;
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_GIF
|
||||
case IMAGE_FORMAT_GIF:
|
||||
GIF_recycle(env, (GIF*) image);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
LOGE(MSG("Can't detect format %d"), format);
|
||||
}
|
||||
}
|
||||
|
||||
int get_supported_formats(int *formats)
|
||||
{
|
||||
int i = 0;
|
||||
#ifdef IMAGE_SUPPORT_JPEG
|
||||
formats[i++] = IMAGE_FORMAT_JPEG;
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_PNG
|
||||
formats[i++] = IMAGE_FORMAT_PNG;
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_GIF
|
||||
formats[i++] = IMAGE_FORMAT_GIF;
|
||||
#endif
|
||||
return i;
|
||||
}
|
||||
|
||||
const char *get_decoder_description(int format)
|
||||
{
|
||||
switch (format) {
|
||||
#ifdef IMAGE_SUPPORT_JPEG
|
||||
case IMAGE_FORMAT_JPEG:
|
||||
return IMAGE_JPEG_DECODER_DESCRIPTION;
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_PNG
|
||||
case IMAGE_FORMAT_PNG:
|
||||
return IMAGE_PNG_DECODER_DESCRIPTION;
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_GIF
|
||||
case IMAGE_FORMAT_GIF:
|
||||
return IMAGE_GIF_DECODER_DESCRIPTION;
|
||||
#endif
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2015 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.
|
||||
*/
|
||||
|
||||
//
|
||||
// Created by Hippo on 12/27/2015.
|
||||
//
|
||||
|
||||
#ifndef IMAGE_IMAGE_H
|
||||
#define IMAGE_IMAGE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "input_stream.h"
|
||||
|
||||
#define IMAGE_FORMAT_UNKNOWN -1
|
||||
#ifdef IMAGE_SUPPORT_PLAIN
|
||||
#define IMAGE_FORMAT_PLAIN 0
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_JPEG
|
||||
#define IMAGE_FORMAT_JPEG 1
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_PNG
|
||||
#define IMAGE_FORMAT_PNG 2
|
||||
#endif
|
||||
#ifdef IMAGE_SUPPORT_GIF
|
||||
#define IMAGE_FORMAT_GIF 3
|
||||
#endif
|
||||
|
||||
#define IMAGE_MAX_SUPPORTED_FORMAT_COUNT 3
|
||||
|
||||
void* decode(JNIEnv* env, InputStream* stream, bool partially, int* format);
|
||||
void* create(unsigned int width, unsigned int height, const void* data);
|
||||
bool complete(JNIEnv* env, void* image, int format);
|
||||
bool is_completed(void* image, int format);
|
||||
int get_width(void* image, int format);
|
||||
int get_height(void* image, int format);
|
||||
int get_byte_count(void* image, int format);
|
||||
void render(void* image, int format, int src_x, int src_y,
|
||||
void* dst, int dst_w, int dst_h, int dst_x, int dst_y,
|
||||
int width, int height, bool fill_blank, int default_color);
|
||||
void advance(void* image, int format);
|
||||
int get_delay(void* image, int format);
|
||||
int get_frame_count(void* image, int format);
|
||||
bool is_opaque(void* image, int format);
|
||||
bool is_gray(void* image, int format, int error);
|
||||
void clahe(void* image, int format, bool to_gray);
|
||||
void recycle(JNIEnv *env, void* image, int format);
|
||||
int get_supported_formats(int *);
|
||||
const char *get_decoder_description(int);
|
||||
|
||||
#endif //IMAGE_IMAGE_H
|
||||
@@ -0,0 +1,558 @@
|
||||
/*
|
||||
* Copyright 2015 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.
|
||||
*/
|
||||
|
||||
//
|
||||
// Created by Hippo on 12/27/2015.
|
||||
//
|
||||
|
||||
#include "config.h"
|
||||
#ifdef IMAGE_SUPPORT_GIF
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "image_gif.h"
|
||||
#include "image_utils.h"
|
||||
#include "java_wrapper.h"
|
||||
#include "../log.h"
|
||||
#include "../utils.h"
|
||||
|
||||
static int error_code = 0;
|
||||
|
||||
typedef struct {
|
||||
unsigned char red;
|
||||
unsigned char green;
|
||||
unsigned char blue;
|
||||
unsigned char alpha;
|
||||
} RGBA;
|
||||
|
||||
static int custom_read_fun(GifFileType* gif, GifByteType* bytes, int size) {
|
||||
bool attach;
|
||||
int read;
|
||||
PatchHeadInputStream* patch_head_input_stream = gif->UserData;
|
||||
JNIEnv *env = obtain_env(&attach);
|
||||
|
||||
if (env == NULL) {
|
||||
LOGE(MSG("Can't get JNIEnv"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
read = read_patch_head_input_stream(env, patch_head_input_stream, bytes, 0, (size_t) size);
|
||||
|
||||
if (attach) {
|
||||
release_env();
|
||||
}
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
static void generate_prepare(GIF_FRAME_INFO* frame_info_array, int count)
|
||||
{
|
||||
int disposal = 0xff;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < count; i++){
|
||||
GIF_FRAME_INFO* frame_info = frame_info_array + i;
|
||||
|
||||
switch (disposal) {
|
||||
case DISPOSAL_UNSPECIFIED:
|
||||
case DISPOSE_DO_NOT:
|
||||
frame_info->prepare = IMAGE_GIF_PREPARE_NONE;
|
||||
break;
|
||||
case DISPOSE_BACKGROUND:
|
||||
frame_info->prepare = IMAGE_GIF_PREPARE_BACKGROUND;
|
||||
break;
|
||||
case DISPOSE_PREVIOUS:
|
||||
frame_info->prepare = IMAGE_GIF_PREPARE_USE_BACKUP;
|
||||
break;
|
||||
default:
|
||||
frame_info->prepare = IMAGE_GIF_PREPARE_BACKGROUND;
|
||||
break;
|
||||
}
|
||||
|
||||
disposal = frame_info->disposal;
|
||||
}
|
||||
}
|
||||
|
||||
static void read_gcb(GifFileType* gif_file, int index, GIF_FRAME_INFO* frame_info)
|
||||
{
|
||||
GraphicsControlBlock gcb;
|
||||
if (DGifSavedExtensionToGCB(gif_file, index, &gcb) == GIF_OK) {
|
||||
frame_info->tran = gcb.TransparentColor;
|
||||
frame_info->delay = gcb.DelayTime * 10;
|
||||
frame_info->disposal = gcb.DisposalMode;
|
||||
frame_info->prepare = IMAGE_GIF_PREPARE_UNKNOWN;
|
||||
} else {
|
||||
frame_info->tran = -1;
|
||||
frame_info->delay = 0;
|
||||
frame_info->disposal = DISPOSE_DO_NOT;
|
||||
frame_info->prepare = IMAGE_GIF_PREPARE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static void fix_gif_file(GifFileType* gif_file) {
|
||||
if (gif_file->ImageCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
SavedImage last_image = gif_file->SavedImages[gif_file->ImageCount - 1];
|
||||
// If RasterBits is NULL, discard this frame
|
||||
if (last_image.RasterBits == NULL) {
|
||||
gif_file->ImageCount--;
|
||||
// Free last image
|
||||
if (last_image.ImageDesc.ColorMap != NULL) {
|
||||
GifFreeMapObject(last_image.ImageDesc.ColorMap);
|
||||
last_image.ImageDesc.ColorMap = NULL;
|
||||
}
|
||||
GifFreeExtensions(&last_image.ExtensionBlockCount, &last_image.ExtensionBlocks);
|
||||
}
|
||||
}
|
||||
|
||||
void* GIF_decode(JNIEnv* env, PatchHeadInputStream* patch_head_input_stream, bool partially)
|
||||
{
|
||||
GIF* gif = NULL;
|
||||
GifFileType* gif_file = NULL;
|
||||
void* buffer = NULL;
|
||||
void*shown_buffer = NULL;
|
||||
GIF_FRAME_INFO* frame_info_array = NULL;
|
||||
int i;
|
||||
|
||||
gif = (GIF*) malloc(sizeof(GIF));
|
||||
if (gif == NULL) {
|
||||
WTF_OM;
|
||||
close_patch_head_input_stream(env, patch_head_input_stream);
|
||||
destroy_patch_head_input_stream(env, &patch_head_input_stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Open
|
||||
gif_file = DGifOpen(patch_head_input_stream, &custom_read_fun, &error_code);
|
||||
if (gif_file == NULL) {
|
||||
WTF_OM;
|
||||
free(gif);
|
||||
close_patch_head_input_stream(env, patch_head_input_stream);
|
||||
destroy_patch_head_input_stream(env, &patch_head_input_stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Buffer
|
||||
buffer = malloc(gif_file->SWidth * gif_file->SHeight * sizeof(RGBA));
|
||||
shown_buffer = malloc(gif_file->SWidth * gif_file->SHeight * sizeof(RGBA));
|
||||
if (buffer == NULL || shown_buffer == NULL) {
|
||||
WTF_OM;
|
||||
free(buffer);
|
||||
free(shown_buffer);
|
||||
DGifCloseFile(gif_file, &error_code);
|
||||
free(gif);
|
||||
close_patch_head_input_stream(env, patch_head_input_stream);
|
||||
destroy_patch_head_input_stream(env, &patch_head_input_stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (partially) {
|
||||
// Glance
|
||||
if (DGifGlance(gif_file) != GIF_OK) {
|
||||
LOGE(MSG("GIF error code %d"), error_code);
|
||||
DGifCloseFile(gif_file, &error_code);
|
||||
free(buffer);
|
||||
free(gif);
|
||||
close_patch_head_input_stream(env, patch_head_input_stream);
|
||||
destroy_patch_head_input_stream(env, &patch_head_input_stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Frame info
|
||||
frame_info_array = (GIF_FRAME_INFO*) malloc(sizeof(GIF_FRAME_INFO));
|
||||
if (frame_info_array == NULL) {
|
||||
WTF_OM;
|
||||
DGifCloseFile(gif_file, &error_code);
|
||||
free(buffer);
|
||||
free(gif);
|
||||
close_patch_head_input_stream(env, patch_head_input_stream);
|
||||
destroy_patch_head_input_stream(env, &patch_head_input_stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Read gcb
|
||||
read_gcb(gif_file, 0, frame_info_array);
|
||||
|
||||
gif->partially = true;
|
||||
gif->patch_head_input_stream = patch_head_input_stream;
|
||||
} else {
|
||||
// Slurp
|
||||
if (DGifSlurp(gif_file) == GIF_ERROR) {
|
||||
fix_gif_file(gif_file);
|
||||
}
|
||||
if (gif_file->ImageCount <= 0) {
|
||||
LOGE(MSG("No frame"));
|
||||
DGifCloseFile(gif_file, &error_code);
|
||||
free(buffer);
|
||||
free(gif);
|
||||
close_patch_head_input_stream(env, patch_head_input_stream);
|
||||
destroy_patch_head_input_stream(env, &patch_head_input_stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Frame info
|
||||
frame_info_array = (GIF_FRAME_INFO*) malloc(gif_file->ImageCount * sizeof(GIF_FRAME_INFO));
|
||||
if (frame_info_array == NULL) {
|
||||
WTF_OM;
|
||||
DGifCloseFile(gif_file, &error_code);
|
||||
free(buffer);
|
||||
free(gif);
|
||||
close_patch_head_input_stream(env, patch_head_input_stream);
|
||||
destroy_patch_head_input_stream(env, &patch_head_input_stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Read gcb
|
||||
for (i = 0; i < gif_file->ImageCount; i++) {
|
||||
read_gcb(gif_file, i, frame_info_array + i);
|
||||
}
|
||||
|
||||
// Generate prepare
|
||||
generate_prepare(frame_info_array, gif_file->ImageCount);
|
||||
|
||||
// Close input stream
|
||||
close_patch_head_input_stream(env, patch_head_input_stream);
|
||||
destroy_patch_head_input_stream(env, &patch_head_input_stream);
|
||||
|
||||
gif->partially = false;
|
||||
gif->patch_head_input_stream = NULL;
|
||||
}
|
||||
|
||||
gif->gif_file = gif_file;
|
||||
gif->frame_info_array = frame_info_array;
|
||||
gif->buffer = buffer;
|
||||
gif->buffer_index = -1;
|
||||
gif->backup = NULL;
|
||||
gif->shown_buffer = shown_buffer;
|
||||
|
||||
GIF_advance(gif);
|
||||
|
||||
return gif;
|
||||
}
|
||||
|
||||
bool GIF_complete(JNIEnv* env, GIF* gif)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!gif->partially) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (gif->gif_file == NULL || gif->patch_head_input_stream == NULL) {
|
||||
LOGE(MSG("Some stuff is NULL"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (DGifSlurp(gif->gif_file) == GIF_ERROR) {
|
||||
fix_gif_file(gif->gif_file);
|
||||
}
|
||||
|
||||
// Close input stream
|
||||
close_patch_head_input_stream(env, gif->patch_head_input_stream);
|
||||
destroy_patch_head_input_stream(env, &gif->patch_head_input_stream);
|
||||
gif->patch_head_input_stream = NULL;
|
||||
|
||||
gif->partially = false;
|
||||
|
||||
gif->frame_info_array = (GIF_FRAME_INFO*) realloc(gif->frame_info_array,
|
||||
gif->gif_file->ImageCount * sizeof(GIF_FRAME_INFO));
|
||||
if (gif->frame_info_array == NULL) {
|
||||
WTF_OM;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read gcb
|
||||
for (i = 0; i < gif->gif_file->ImageCount; i++) {
|
||||
read_gcb(gif->gif_file, i, gif->frame_info_array + i);
|
||||
}
|
||||
|
||||
// Generate prepare
|
||||
generate_prepare(gif->frame_info_array, gif->gif_file->ImageCount);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GIF_is_completed(GIF* gif)
|
||||
{
|
||||
return !gif->partially;
|
||||
}
|
||||
|
||||
void* GIF_get_pixels(GIF* gif)
|
||||
{
|
||||
if (!gif->partially && GIF_get_frame_count(gif) == 1) {
|
||||
return gif->buffer;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int GIF_get_width(GIF* gif)
|
||||
{
|
||||
return gif->gif_file->SWidth;
|
||||
}
|
||||
|
||||
int GIF_get_height(GIF* gif)
|
||||
{
|
||||
return gif->gif_file->SHeight;
|
||||
}
|
||||
|
||||
int GIF_get_byte_count(GIF* gif)
|
||||
{
|
||||
int i;
|
||||
SavedImage saved_image;
|
||||
int size = 0;
|
||||
// Add buffer size
|
||||
if (gif->buffer != NULL) {
|
||||
size += gif->gif_file->SWidth * gif->gif_file->SHeight * 4;
|
||||
}
|
||||
// Add backup size
|
||||
if (gif->backup != NULL) {
|
||||
size += gif->gif_file->SWidth * gif->gif_file->SHeight * 4;
|
||||
}
|
||||
// Add shown buffer size
|
||||
if (gif->shown_buffer != NULL) {
|
||||
size += gif->gif_file->SWidth * gif->gif_file->SHeight * 4;
|
||||
}
|
||||
// Add frames size
|
||||
if (gif->gif_file->SavedImages != NULL) {
|
||||
for (i = 0; i < gif->gif_file->ImageCount; i++) {
|
||||
saved_image = gif->gif_file->SavedImages[i];
|
||||
if (saved_image.RasterBits != NULL) {
|
||||
size += saved_image.ImageDesc.Width * saved_image.ImageDesc.Height * 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
void GIF_render(GIF* gif, int src_x, int src_y,
|
||||
void* dst, int dst_w, int dst_h, int dst_x, int dst_y,
|
||||
int width, int height, bool fill_blank, int default_color)
|
||||
{
|
||||
copy_pixels(gif->shown_buffer, gif->gif_file->SWidth, gif->gif_file->SHeight, src_x, src_y,
|
||||
dst, dst_w, dst_h, dst_x, dst_y,
|
||||
width, height, fill_blank, default_color);
|
||||
}
|
||||
|
||||
static void backup(GIF* gif)
|
||||
{
|
||||
if (gif->backup == NULL) {
|
||||
gif->backup = (unsigned char*) malloc((size_t) (gif->gif_file->SWidth * gif->gif_file->SHeight * 4));
|
||||
if (gif->backup == NULL) {
|
||||
WTF_OM;
|
||||
return;
|
||||
}
|
||||
}
|
||||
memcpy(gif->backup, gif->buffer, (size_t) (gif->gif_file->SWidth * gif->gif_file->SHeight * 4));
|
||||
}
|
||||
|
||||
static void restore(GIF* gif)
|
||||
{
|
||||
if (gif->backup == NULL) {
|
||||
LOGE(MSG("Backup is NULL"));
|
||||
} else {
|
||||
memcpy(gif->buffer, gif->backup, (size_t) (gif->gif_file->SWidth * gif->gif_file->SHeight * 4));
|
||||
}
|
||||
}
|
||||
|
||||
static void switch_buffer_backup(GIF* gif)
|
||||
{
|
||||
unsigned char* temp;
|
||||
|
||||
if (gif->backup == NULL) {
|
||||
backup(gif);
|
||||
} else {
|
||||
temp = gif->buffer;
|
||||
gif->buffer = gif->backup;
|
||||
gif->backup = temp;
|
||||
}
|
||||
}
|
||||
|
||||
static void clear(RGBA* pixels, int num, RGBA color) {
|
||||
RGBA* ptr = pixels;
|
||||
int i;
|
||||
for (i = 0; i < num; i++) {
|
||||
*(ptr++) = color;
|
||||
}
|
||||
}
|
||||
|
||||
static bool get_color_from_table(const ColorMapObject* cmap, int index,
|
||||
RGBA* color) {
|
||||
if (cmap == NULL || index < 0 || index >= cmap->ColorCount) {
|
||||
return false;
|
||||
} else {
|
||||
GifColorType gct = cmap->Colors[index];
|
||||
color->red = gct.Red;
|
||||
color->green = gct.Green;
|
||||
color->blue = gct.Blue;
|
||||
color->alpha = 0xff;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static void clear_bg(GifFileType* gif_file, void* pixels)
|
||||
{
|
||||
RGBA color;
|
||||
if (!get_color_from_table(gif_file->SColorMap, gif_file->SBackGroundColor, &color)) {
|
||||
color.red = 0x00;
|
||||
color.green = 0x00;
|
||||
color.blue = 0x00;
|
||||
color.alpha = 0x00;
|
||||
}
|
||||
clear(pixels, gif_file->SWidth * gif_file->SHeight, color);
|
||||
}
|
||||
|
||||
static void copy_line(GifByteType* src, RGBA* dst,
|
||||
const ColorMapObject* cmap, int tran, int len)
|
||||
{
|
||||
for (; len > 0; len--, src++, dst++) {
|
||||
int index = *src;
|
||||
if (tran == -1 || index != tran) {
|
||||
get_color_from_table(cmap, index, dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void blend(GifFileType* gif_file, int index, void* pixels, int tran)
|
||||
{
|
||||
int width = gif_file->SWidth;
|
||||
int height = gif_file->SHeight;
|
||||
SavedImage cur = gif_file->SavedImages[index];
|
||||
GifImageDesc desc = cur.ImageDesc;
|
||||
int copy_width = MIN(width - desc.Left, desc.Width);
|
||||
int copy_height = MIN(height - desc.Top, desc.Height);
|
||||
ColorMapObject *cmap = desc.ColorMap;
|
||||
GifByteType* src = cur.RasterBits;
|
||||
RGBA* dst = pixels;
|
||||
GifByteType* src_ptr;
|
||||
RGBA* dst_ptr;
|
||||
size_t len;
|
||||
int i;
|
||||
|
||||
if (cmap == NULL) {
|
||||
cmap = gif_file->SColorMap;
|
||||
}
|
||||
if (cmap != NULL) {
|
||||
for (i = 0; i < copy_height; i++) {
|
||||
src_ptr = src + (i * desc.Width);
|
||||
dst_ptr = dst + ((desc.Top + i) * width + desc.Left);
|
||||
len = (size_t) copy_width;
|
||||
copy_line(src_ptr, dst_ptr, cmap, tran, len);
|
||||
}
|
||||
} else {
|
||||
LOGW(MSG("Can't find color map"));
|
||||
}
|
||||
}
|
||||
|
||||
void GIF_advance(GIF* gif)
|
||||
{
|
||||
int index;
|
||||
GIF_FRAME_INFO frame_info;
|
||||
|
||||
index = (gif->buffer_index + 1) % gif->gif_file->ImageCount;
|
||||
if (index != 0 && gif->partially) {
|
||||
LOGE(MSG("The png is only decoded partially. Only the first frame can be shown."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (gif->frame_info_array == NULL) {
|
||||
frame_info.tran = -1;
|
||||
frame_info.disposal = DISPOSE_DO_NOT;
|
||||
frame_info.delay = 0;
|
||||
frame_info.prepare = IMAGE_GIF_PREPARE_BACKGROUND;
|
||||
} else {
|
||||
memcpy(&frame_info, gif->frame_info_array + index, sizeof(GIF_FRAME_INFO));
|
||||
}
|
||||
|
||||
if (frame_info.disposal == DISPOSE_PREVIOUS && frame_info.prepare == IMAGE_GIF_PREPARE_USE_BACKUP) {
|
||||
switch_buffer_backup(gif);
|
||||
} else {
|
||||
// Backup
|
||||
if (frame_info.disposal == DISPOSE_PREVIOUS) {
|
||||
backup(gif);
|
||||
}
|
||||
|
||||
// Prepare
|
||||
switch (frame_info.prepare) {
|
||||
case IMAGE_GIF_PREPARE_NONE:
|
||||
// Do nothing
|
||||
break;
|
||||
default:
|
||||
case IMAGE_GIF_PREPARE_BACKGROUND:
|
||||
// Set bg
|
||||
clear_bg(gif->gif_file, gif->buffer);
|
||||
break;
|
||||
case IMAGE_GIF_PREPARE_USE_BACKUP:
|
||||
restore(gif);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
blend(gif->gif_file, index, gif->buffer, frame_info.tran);
|
||||
|
||||
// Copy to shown buffer
|
||||
memcpy(gif->shown_buffer, gif->buffer, (size_t) (gif->gif_file->SWidth * gif->gif_file->SHeight * 4));
|
||||
|
||||
gif->buffer_index = index;
|
||||
}
|
||||
|
||||
int GIF_get_delay(GIF* gif)
|
||||
{
|
||||
if (gif->frame_info_array == NULL) {
|
||||
return 0;
|
||||
} else {
|
||||
return gif->frame_info_array[gif->buffer_index].delay;
|
||||
}
|
||||
}
|
||||
|
||||
int GIF_get_frame_count(GIF* gif)
|
||||
{
|
||||
return gif->gif_file->ImageCount;
|
||||
}
|
||||
|
||||
bool GIF_is_opaque(GIF* gif)
|
||||
{
|
||||
return gif->frame_info_array->tran < 0;
|
||||
}
|
||||
|
||||
void GIF_recycle(JNIEnv* env, GIF* gif)
|
||||
{
|
||||
DGifCloseFile(gif->gif_file, &error_code);
|
||||
gif->gif_file = NULL;
|
||||
|
||||
free(gif->frame_info_array);
|
||||
gif->frame_info_array = NULL;
|
||||
|
||||
free(gif->buffer);
|
||||
gif->buffer = NULL;
|
||||
|
||||
free(gif->backup);
|
||||
gif->backup = NULL;
|
||||
|
||||
free(gif->shown_buffer);
|
||||
gif->shown_buffer = NULL;
|
||||
|
||||
if (gif->patch_head_input_stream != NULL) {
|
||||
close_patch_head_input_stream(env, gif->patch_head_input_stream);
|
||||
destroy_patch_head_input_stream(env, &gif->patch_head_input_stream);
|
||||
gif->patch_head_input_stream = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // IMAGE_SUPPORT_GIF
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2015 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.
|
||||
*/
|
||||
|
||||
//
|
||||
// Created by Hippo on 12/27/2015.
|
||||
//
|
||||
|
||||
#ifndef IMAGE_IMAGE_GIF_H
|
||||
#define IMAGE_IMAGE_GIF_H
|
||||
|
||||
#include "config.h"
|
||||
#ifdef IMAGE_SUPPORT_GIF
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "patch_head_input_stream.h"
|
||||
#include "gif_lib.h"
|
||||
#include "../utils.h"
|
||||
|
||||
#define IMAGE_GIF_DECODER_DESCRIPTION ("giflib " MAKESTRING(STRINGIZE, GIFLIB_MAJOR) "." \
|
||||
MAKESTRING(STRINGIZE, GIFLIB_MINOR) "." \
|
||||
MAKESTRING(STRINGIZE, GIFLIB_RELEASE))
|
||||
|
||||
#define IMAGE_GIF_MAGIC_NUMBER_0 0x47
|
||||
#define IMAGE_GIF_MAGIC_NUMBER_1 0x49
|
||||
|
||||
#define IMAGE_GIF_PREPARE_UNKNOWN 0x00
|
||||
#define IMAGE_GIF_PREPARE_NONE 0x01
|
||||
#define IMAGE_GIF_PREPARE_BACKGROUND 0x02
|
||||
#define IMAGE_GIF_PREPARE_USE_BACKUP 0x03
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int tran;
|
||||
int disposal;
|
||||
int delay;
|
||||
int prepare;
|
||||
} GIF_FRAME_INFO;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GifFileType* gif_file;
|
||||
GIF_FRAME_INFO* frame_info_array;
|
||||
void* buffer;
|
||||
int buffer_index;
|
||||
void* backup;
|
||||
bool partially;
|
||||
// Use extra buffer to avoid blink
|
||||
void* shown_buffer;
|
||||
PatchHeadInputStream* patch_head_input_stream;
|
||||
} GIF;
|
||||
|
||||
void* GIF_decode(JNIEnv* env, PatchHeadInputStream* patch_head_input_stream, bool partially);
|
||||
bool GIF_complete(JNIEnv* env, GIF* gif);
|
||||
bool GIF_is_completed(GIF* gif);
|
||||
void* GIF_get_pixels(GIF* gif);
|
||||
int GIF_get_width(GIF* gif);
|
||||
int GIF_get_height(GIF* gif);
|
||||
int GIF_get_byte_count(GIF* gif);
|
||||
void GIF_render(GIF* gif, int src_x, int src_y,
|
||||
void* dst, int dst_w, int dst_h, int dst_x, int dst_y,
|
||||
int width, int height, bool fill_blank, int default_color);
|
||||
void GIF_advance(GIF* gif);
|
||||
int GIF_get_delay(GIF* gif);
|
||||
int GIF_get_frame_count(GIF* gif);
|
||||
bool GIF_is_opaque(GIF* gif);
|
||||
void GIF_recycle(JNIEnv* env, GIF* gif);
|
||||
|
||||
#endif // IMAGE_SUPPORT_GIF
|
||||
|
||||
#endif // IMAGE_IMAGE_GIF_H
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
//
|
||||
// Created by Hippo on 1/3/2016.
|
||||
//
|
||||
|
||||
#include "config.h"
|
||||
#ifdef IMAGE_SUPPORT_JPEG
|
||||
|
||||
#include <setjmp.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "image_jpeg.h"
|
||||
#include "image_utils.h"
|
||||
#include "java_wrapper.h"
|
||||
#include "../log.h"
|
||||
|
||||
struct my_error_mgr {
|
||||
struct jpeg_error_mgr pub;
|
||||
jmp_buf setjmp_buffer;
|
||||
};
|
||||
|
||||
typedef struct my_error_mgr * my_error_ptr;
|
||||
|
||||
static char emsg[JMSG_LENGTH_MAX];
|
||||
|
||||
static void my_error_exit(j_common_ptr cinfo)
|
||||
{
|
||||
my_error_ptr myerr = (my_error_ptr) cinfo->err;
|
||||
(*cinfo->err->format_message)(cinfo, emsg);
|
||||
longjmp(myerr->setjmp_buffer, 1);
|
||||
}
|
||||
|
||||
static size_t custom_read(void * custom_stuff, unsigned char * buffer, size_t size)
|
||||
{
|
||||
bool attach;
|
||||
size_t read;
|
||||
PatchHeadInputStream* patch_head_input_stream = custom_stuff;
|
||||
JNIEnv *env = obtain_env(&attach);
|
||||
|
||||
if (env == NULL) {
|
||||
LOGE(MSG("Can't get JNIEnv"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
read = read_patch_head_input_stream(env, patch_head_input_stream, buffer, 0, size);
|
||||
|
||||
if (attach) {
|
||||
release_env();
|
||||
}
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
void* JPEG_decode(JNIEnv* env, PatchHeadInputStream* patch_head_input_stream, bool partially)
|
||||
{
|
||||
JPEG* jpeg = NULL;
|
||||
struct jpeg_decompress_struct cinfo;
|
||||
struct my_error_mgr jerr;
|
||||
unsigned char* buffer = NULL;
|
||||
size_t stride;
|
||||
unsigned char* line_buffer_array[3];
|
||||
int read_lines;
|
||||
|
||||
jpeg = (JPEG*) malloc(sizeof(JPEG));
|
||||
if (jpeg == NULL) {
|
||||
WTF_OM;
|
||||
close_patch_head_input_stream(env, patch_head_input_stream);
|
||||
destroy_patch_head_input_stream(env, &patch_head_input_stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Init
|
||||
cinfo.err = jpeg_std_error(&jerr.pub);
|
||||
jerr.pub.error_exit = my_error_exit;
|
||||
if (setjmp(jerr.setjmp_buffer)) {
|
||||
LOGE(MSG("%s"), emsg);
|
||||
free(jpeg);
|
||||
free(buffer);
|
||||
jpeg_destroy_decompress(&cinfo);
|
||||
close_patch_head_input_stream(env, patch_head_input_stream);
|
||||
destroy_patch_head_input_stream(env, &patch_head_input_stream);
|
||||
return NULL;
|
||||
}
|
||||
jpeg_create_decompress(&cinfo);
|
||||
jpeg_custom_src(&cinfo, &custom_read, patch_head_input_stream);
|
||||
jpeg_read_header(&cinfo, TRUE);
|
||||
|
||||
// Start decompress
|
||||
cinfo.out_color_space = JCS_EXT_RGBA;
|
||||
jpeg_start_decompress(&cinfo);
|
||||
|
||||
stride = cinfo.output_components * cinfo.output_width;
|
||||
buffer = malloc(stride * cinfo.output_height);
|
||||
if (buffer == NULL) {
|
||||
free(jpeg);
|
||||
jpeg_destroy_decompress(&cinfo);
|
||||
close_patch_head_input_stream(env, patch_head_input_stream);
|
||||
destroy_patch_head_input_stream(env, &patch_head_input_stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Copy buffer
|
||||
line_buffer_array[0] = buffer;
|
||||
line_buffer_array[1] = line_buffer_array[0] + stride;
|
||||
line_buffer_array[2] = line_buffer_array[1] + stride;
|
||||
while (cinfo.output_scanline < cinfo.output_height) {
|
||||
read_lines = jpeg_read_scanlines(&cinfo, line_buffer_array, 3);
|
||||
line_buffer_array[0] += stride * read_lines;
|
||||
line_buffer_array[1] = line_buffer_array[0] + stride;
|
||||
line_buffer_array[2] = line_buffer_array[1] + stride;
|
||||
}
|
||||
|
||||
// Finish decompress
|
||||
jpeg_finish_decompress(&cinfo);
|
||||
jpeg_destroy_decompress(&cinfo);
|
||||
|
||||
// Close stream
|
||||
close_patch_head_input_stream(env, patch_head_input_stream);
|
||||
destroy_patch_head_input_stream(env, &patch_head_input_stream);
|
||||
|
||||
// Fill jpeg
|
||||
jpeg->width = cinfo.output_width;
|
||||
jpeg->height = cinfo.output_height;
|
||||
jpeg->buffer = buffer;
|
||||
|
||||
return jpeg;
|
||||
}
|
||||
|
||||
bool JPEG_complete(JPEG* jpeg)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool JPEG_is_completed(JPEG* jpeg)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void* JPEG_get_pixels(JPEG* jpeg)
|
||||
{
|
||||
return jpeg->buffer;
|
||||
}
|
||||
|
||||
int JPEG_get_width(JPEG* jpeg)
|
||||
{
|
||||
return jpeg->width;
|
||||
}
|
||||
|
||||
int JPEG_get_height(JPEG* jpeg)
|
||||
{
|
||||
return jpeg->height;
|
||||
}
|
||||
|
||||
int JPEG_get_byte_count(JPEG* jpeg)
|
||||
{
|
||||
return jpeg->width * jpeg->height * 4;
|
||||
}
|
||||
|
||||
void JPEG_render(JPEG* jpeg, int src_x, int src_y,
|
||||
void* dst, int dst_w, int dst_h, int dst_x, int dst_y,
|
||||
int width, int height, bool fill_blank, int default_color)
|
||||
{
|
||||
copy_pixels(jpeg->buffer, jpeg->width, jpeg->height, src_x, src_y,
|
||||
dst, dst_w, dst_h, dst_x, dst_y,
|
||||
width, height, fill_blank, default_color);
|
||||
}
|
||||
|
||||
void JPEG_advance(JPEG* jpeg)
|
||||
{
|
||||
}
|
||||
|
||||
int JPEG_get_delay(JPEG* jpeg)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int JPEG_get_frame_count(JPEG* jpeg)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool JPEG_is_opaque(JPEG* jpeg)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void JPEG_recycle(JPEG* jpeg)
|
||||
{
|
||||
free(jpeg->buffer);
|
||||
jpeg->buffer = NULL;
|
||||
|
||||
free(jpeg);
|
||||
}
|
||||
|
||||
#endif // IMAGE_SUPPORT_JPEG
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
//
|
||||
// Created by Hippo on 1/3/2016.
|
||||
//
|
||||
|
||||
#ifndef IMAGE_IMAGE_JPEG_H
|
||||
#define IMAGE_IMAGE_JPEG_H
|
||||
|
||||
#include "config.h"
|
||||
#ifdef IMAGE_SUPPORT_JPEG
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "jpeglib.h"
|
||||
#include "patch_head_input_stream.h"
|
||||
#include "../utils.h"
|
||||
|
||||
#define IMAGE_JPEG_DECODER_DESCRIPTION ("libjpeg-turbo " MAKESTRING(STRINGIZE, LIBJPEG_TURBO_VERSION))
|
||||
|
||||
#define IMAGE_JPEG_MAGIC_NUMBER_0 0xFF
|
||||
#define IMAGE_JPEG_MAGIC_NUMBER_1 0xD8
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
void* buffer;
|
||||
} JPEG;
|
||||
|
||||
void* JPEG_decode(JNIEnv* env, PatchHeadInputStream* patch_head_input_stream, bool partially);
|
||||
bool JPEG_complete(JPEG* jpeg);
|
||||
bool JPEG_is_completed(JPEG* jpeg);
|
||||
void* JPEG_get_pixels(JPEG* jpeg);
|
||||
int JPEG_get_width(JPEG* jpeg);
|
||||
int JPEG_get_height(JPEG* jpeg);
|
||||
int JPEG_get_byte_count(JPEG* jpeg);
|
||||
void JPEG_render(JPEG* jpeg, int src_x, int src_y,
|
||||
void* dst, int dst_w, int dst_h, int dst_x, int dst_y,
|
||||
int width, int height, bool fill_blank, int default_color);
|
||||
void JPEG_advance(JPEG* jpeg);
|
||||
int JPEG_get_delay(JPEG* jpeg);
|
||||
int JPEG_get_frame_count(JPEG* jpeg);
|
||||
bool JPEG_is_opaque(JPEG* jpeg);
|
||||
void JPEG_recycle(JPEG* jpeg);
|
||||
|
||||
#endif // IMAGE_SUPPORT_JPEG
|
||||
|
||||
#endif // IMAGE_IMAGE_JPEG_H
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
//
|
||||
// Created by Hippo on 5/3/2016.
|
||||
//
|
||||
|
||||
#include "config.h"
|
||||
#ifdef IMAGE_SUPPORT_PLAIN
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "image_plain.h"
|
||||
#include "image_utils.h"
|
||||
#include "../log.h"
|
||||
|
||||
void* PLAIN_create(unsigned int width, unsigned int height, const void* data)
|
||||
{
|
||||
PLAIN* plain = NULL;
|
||||
void* buffer = NULL;
|
||||
size_t length;
|
||||
|
||||
plain = (PLAIN*) malloc(sizeof(PLAIN));
|
||||
if (plain == NULL) {
|
||||
WTF_OM;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
length = (size_t) (width * height * 4);
|
||||
|
||||
buffer = malloc(length);
|
||||
if (buffer == NULL) {
|
||||
WTF_OM;
|
||||
free(plain);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(buffer, data, length);
|
||||
|
||||
plain->width = width;
|
||||
plain->height = height;
|
||||
plain->buffer = buffer;
|
||||
|
||||
return plain;
|
||||
}
|
||||
|
||||
bool PLAIN_complete(PLAIN* plain)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PLAIN_is_completed(PLAIN* plain)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void* PLAIN_get_pixels(PLAIN* plain)
|
||||
{
|
||||
return plain->buffer;
|
||||
}
|
||||
|
||||
int PLAIN_get_width(PLAIN* plain)
|
||||
{
|
||||
return plain->width;
|
||||
}
|
||||
|
||||
int PLAIN_get_height(PLAIN* plain)
|
||||
{
|
||||
return plain->height;
|
||||
}
|
||||
|
||||
int PLAIN_get_byte_count(PLAIN* plain)
|
||||
{
|
||||
return plain->width * plain->height * 4;
|
||||
}
|
||||
|
||||
void PLAIN_render(PLAIN* plain, int src_x, int src_y,
|
||||
void* dst, int dst_w, int dst_h, int dst_x, int dst_y,
|
||||
int width, int height, bool fill_blank, int default_color)
|
||||
{
|
||||
copy_pixels(plain->buffer, plain->width, plain->height, src_x, src_y,
|
||||
dst, dst_w, dst_h, dst_x, dst_y,
|
||||
width, height, fill_blank, default_color);
|
||||
}
|
||||
|
||||
void PLAIN_advance(PLAIN* plain)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
int PLAIN_get_delay(PLAIN* plain)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int PLAIN_get_frame_count(PLAIN* plain)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool PLAIN_is_opaque(PLAIN* plain)
|
||||
{
|
||||
// TODO Check plain all alpha
|
||||
return false;
|
||||
}
|
||||
|
||||
void PLAIN_recycle(PLAIN* plain)
|
||||
{
|
||||
free(plain->buffer);
|
||||
plain->buffer = NULL;
|
||||
|
||||
free(plain);
|
||||
}
|
||||
|
||||
#endif // IMAGE_SUPPORT_PLAIN
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
//
|
||||
// Created by Hippo on 5/3/2016.
|
||||
//
|
||||
|
||||
#ifndef IMAGE_IMAGE_PLAIN_H
|
||||
#define IMAGE_IMAGE_PLAIN_H
|
||||
|
||||
#include "config.h"
|
||||
#ifdef IMAGE_SUPPORT_PLAIN
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "patch_head_input_stream.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
void* buffer;
|
||||
} PLAIN;
|
||||
|
||||
void* PLAIN_create(unsigned int width, unsigned int height, const void* data);
|
||||
bool PLAIN_complete(PLAIN* plain);
|
||||
bool PLAIN_is_completed(PLAIN* plain);
|
||||
void* PLAIN_get_pixels(PLAIN* plain);
|
||||
int PLAIN_get_width(PLAIN* plain);
|
||||
int PLAIN_get_height(PLAIN* plain);
|
||||
int PLAIN_get_byte_count(PLAIN* plain);
|
||||
void PLAIN_render(PLAIN* plain, int src_x, int src_y,
|
||||
void* dst, int dst_w, int dst_h, int dst_x, int dst_y,
|
||||
int width, int height, bool fill_blank, int default_color);
|
||||
void PLAIN_advance(PLAIN* plain);
|
||||
int PLAIN_get_delay(PLAIN* plain);
|
||||
int PLAIN_get_frame_count(PLAIN* plain);
|
||||
bool PLAIN_is_opaque(PLAIN* plain);
|
||||
void PLAIN_recycle(PLAIN* plain);
|
||||
|
||||
#endif // IMAGE_SUPPORT_PLAIN
|
||||
|
||||
#endif // IMAGE_IMAGE_PLAIN_H
|
||||
@@ -0,0 +1,642 @@
|
||||
/*
|
||||
* Copyright 2015 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.
|
||||
*/
|
||||
|
||||
//
|
||||
// Created by Hippo on 12/27/2015.
|
||||
//
|
||||
|
||||
#include "config.h"
|
||||
#ifdef IMAGE_SUPPORT_PNG
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "../log.h"
|
||||
#include "../utils.h"
|
||||
#include "image_png.h"
|
||||
#include "image_utils.h"
|
||||
#include "java_wrapper.h"
|
||||
|
||||
static void user_read_fn(png_structp png_ptr,
|
||||
png_bytep data, png_size_t length)
|
||||
{
|
||||
bool attach;
|
||||
PatchHeadInputStream* patch_head_input_stream = png_get_io_ptr(png_ptr);
|
||||
JNIEnv *env = obtain_env(&attach);
|
||||
|
||||
if (env == NULL) {
|
||||
LOGE(MSG("Can't get JNIEnv"));
|
||||
}
|
||||
|
||||
read_patch_head_input_stream(env, patch_head_input_stream, data, 0, length);
|
||||
|
||||
if (attach) {
|
||||
release_env();
|
||||
}
|
||||
}
|
||||
|
||||
static void user_error_fn(png_structp png_ptr,
|
||||
png_const_charp error_msg)
|
||||
{
|
||||
LOGE(MSG("%s"), error_msg);
|
||||
}
|
||||
|
||||
static void user_warn_fn(png_structp png_ptr,
|
||||
png_const_charp error_msg)
|
||||
{
|
||||
LOGW(MSG("%s"), error_msg);
|
||||
}
|
||||
|
||||
static void free_frame_info_array(PNG_FRAME_INFO* frame_info_array, unsigned int count)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (frame_info_array == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
PNG_FRAME_INFO* frame_info = frame_info_array + i;
|
||||
free(frame_info->buffer);
|
||||
frame_info->buffer = NULL;
|
||||
}
|
||||
free(frame_info_array);
|
||||
}
|
||||
|
||||
static bool read_image(png_structp png_ptr, unsigned char* buffer, unsigned int width, unsigned int height)
|
||||
{
|
||||
int i;
|
||||
unsigned char** line_buffer_ptr_array = (unsigned char**) malloc(height * sizeof(unsigned char*));
|
||||
if (line_buffer_ptr_array == NULL) {
|
||||
WTF_OM;
|
||||
return false;
|
||||
}
|
||||
for (i = 0; i < height; i++) {
|
||||
line_buffer_ptr_array[i] = buffer + (width * i * 4);
|
||||
}
|
||||
png_read_image(png_ptr, line_buffer_ptr_array);
|
||||
free(line_buffer_ptr_array);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void read_frame(png_structp png_ptr, png_infop info_ptr, PNG_FRAME_INFO* frame_info)
|
||||
{
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
unsigned int offset_x;
|
||||
unsigned int offset_y;
|
||||
unsigned short delay_num;
|
||||
unsigned short delay_den;
|
||||
unsigned char dop;
|
||||
unsigned char bop;
|
||||
unsigned int delay;
|
||||
unsigned char* buffer = NULL;
|
||||
|
||||
png_read_frame_head(png_ptr, info_ptr);
|
||||
png_get_next_frame_fcTL(png_ptr, info_ptr, &width, &height, &offset_x, &offset_y, &delay_num, &delay_den, &dop, &bop);
|
||||
|
||||
delay = 1000u * delay_num / delay_den;
|
||||
|
||||
frame_info->width = width;
|
||||
frame_info->height = height;
|
||||
frame_info->offset_x = offset_x;
|
||||
frame_info->offset_y = offset_y;
|
||||
frame_info->delay = delay;
|
||||
frame_info->dop = dop;
|
||||
frame_info->bop = bop;
|
||||
frame_info->pop = IMAGE_PNG_PREPARE_UNKNOWN;
|
||||
|
||||
buffer = (unsigned char*) malloc(width * height * 4);
|
||||
if (buffer == NULL) {
|
||||
frame_info->buffer = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
read_image(png_ptr, buffer, width, height);
|
||||
frame_info->buffer = buffer;
|
||||
}
|
||||
|
||||
static void generate_pop(PNG_FRAME_INFO* frame_info_array, int count)
|
||||
{
|
||||
unsigned char dop = 0xff;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < count; i++){
|
||||
PNG_FRAME_INFO* frame_info = frame_info_array + i;
|
||||
|
||||
switch (dop) {
|
||||
case PNG_DISPOSE_OP_NONE:
|
||||
frame_info->pop = IMAGE_PNG_PREPARE_NONE;
|
||||
break;
|
||||
case PNG_DISPOSE_OP_BACKGROUND:
|
||||
frame_info->pop = IMAGE_PNG_PREPARE_BACKGROUND;
|
||||
break;
|
||||
case PNG_DISPOSE_OP_PREVIOUS:
|
||||
frame_info->pop = IMAGE_PNG_PREPARE_USE_BACKUP;
|
||||
break;
|
||||
default:
|
||||
frame_info->pop = IMAGE_PNG_PREPARE_BACKGROUND;
|
||||
break;
|
||||
}
|
||||
|
||||
dop = frame_info->dop;
|
||||
}
|
||||
}
|
||||
|
||||
void* PNG_decode(JNIEnv* env, PatchHeadInputStream* patch_head_input_stream, bool partially)
|
||||
{
|
||||
PNG *png = NULL;
|
||||
png_structp png_ptr = NULL;
|
||||
png_infop info_ptr = NULL;
|
||||
bool apng;
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
int color_type;
|
||||
int bit_depth;
|
||||
bool is_opaque;
|
||||
unsigned char* buffer = NULL;
|
||||
unsigned int frame_count = 0;
|
||||
bool hide_first_frame = false;
|
||||
PNG_FRAME_INFO* frame_info_array = NULL;
|
||||
int i;
|
||||
|
||||
png = (PNG *) malloc(sizeof(PNG));
|
||||
if (png == NULL) {
|
||||
WTF_OM;
|
||||
close_patch_head_input_stream(env, patch_head_input_stream);
|
||||
destroy_patch_head_input_stream(env, &patch_head_input_stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, &user_error_fn, &user_warn_fn);
|
||||
if (png_ptr == NULL) {
|
||||
free(png);
|
||||
png = NULL;
|
||||
close_patch_head_input_stream(env, patch_head_input_stream);
|
||||
destroy_patch_head_input_stream(env, &patch_head_input_stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
info_ptr = png_create_info_struct(png_ptr);
|
||||
if (info_ptr == NULL) {
|
||||
png_destroy_read_struct(&png_ptr, NULL, NULL);
|
||||
free(png);
|
||||
png = NULL;
|
||||
close_patch_head_input_stream(env, patch_head_input_stream);
|
||||
destroy_patch_head_input_stream(env, &patch_head_input_stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr))) {
|
||||
LOGE(MSG("Error in png decode"));
|
||||
free_frame_info_array(frame_info_array, frame_count);
|
||||
frame_info_array = NULL;
|
||||
free(buffer);
|
||||
buffer = NULL;
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
||||
free(png);
|
||||
png = NULL;
|
||||
close_patch_head_input_stream(env, patch_head_input_stream);
|
||||
destroy_patch_head_input_stream(env, &patch_head_input_stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Set custom read function
|
||||
png_set_read_fn(png_ptr, patch_head_input_stream, &user_read_fn);
|
||||
|
||||
// Get png info
|
||||
png_read_info(png_ptr, info_ptr);
|
||||
|
||||
// Check apng
|
||||
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL)) {
|
||||
apng = true;
|
||||
} else {
|
||||
apng = false;
|
||||
}
|
||||
|
||||
// PNG info
|
||||
width = png_get_image_width(png_ptr, info_ptr);
|
||||
height = png_get_image_height(png_ptr, info_ptr);
|
||||
color_type = png_get_color_type(png_ptr, info_ptr);
|
||||
bit_depth = png_get_bit_depth(png_ptr, info_ptr);
|
||||
|
||||
// Create buffer
|
||||
buffer = (unsigned char*) malloc(width * height * 4);
|
||||
if (buffer == NULL) {
|
||||
WTF_OM;
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
||||
free(png);
|
||||
png = NULL;
|
||||
close_patch_head_input_stream(env, patch_head_input_stream);
|
||||
destroy_patch_head_input_stream(env, &patch_head_input_stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (apng) {
|
||||
// Get frame count
|
||||
frame_count = png_get_num_frames(png_ptr, info_ptr);
|
||||
hide_first_frame = png_get_first_frame_is_hidden(png_ptr, info_ptr);
|
||||
if (hide_first_frame) {
|
||||
frame_count--;
|
||||
}
|
||||
|
||||
// Create frame info array
|
||||
frame_info_array = (PNG_FRAME_INFO*) calloc(frame_count, sizeof(PNG_FRAME_INFO));
|
||||
if (frame_info_array == NULL) {
|
||||
WTF_OM;
|
||||
free(buffer);
|
||||
buffer = NULL;
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
||||
free(png);
|
||||
png = NULL;
|
||||
close_patch_head_input_stream(env, patch_head_input_stream);
|
||||
destroy_patch_head_input_stream(env, &patch_head_input_stream);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Configure to ARGB
|
||||
png_set_expand(png_ptr);
|
||||
if (bit_depth == 16) {
|
||||
png_set_scale_16(png_ptr);
|
||||
}
|
||||
if (color_type == PNG_COLOR_TYPE_GRAY ||
|
||||
color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
|
||||
png_set_gray_to_rgb(png_ptr);
|
||||
}
|
||||
if (!(color_type & PNG_COLOR_MASK_ALPHA)) {
|
||||
is_opaque = true;
|
||||
png_set_add_alpha(png_ptr, 0xff, PNG_FILLER_AFTER);
|
||||
} else {
|
||||
is_opaque = false;
|
||||
}
|
||||
|
||||
if (apng) {
|
||||
if (hide_first_frame) {
|
||||
// Skip first frame
|
||||
read_image(png_ptr, buffer, width, height);
|
||||
}
|
||||
|
||||
// Read first frame
|
||||
read_frame(png_ptr, info_ptr, frame_info_array);
|
||||
// Fix dop
|
||||
if (frame_info_array->dop == PNG_DISPOSE_OP_PREVIOUS) {
|
||||
frame_info_array->dop = PNG_DISPOSE_OP_BACKGROUND;
|
||||
}
|
||||
|
||||
if (!partially || frame_count == 1) {
|
||||
// Read all frame
|
||||
for (i = 1; i < frame_count; read_frame(png_ptr, info_ptr, frame_info_array + i++));
|
||||
|
||||
// Generate pop
|
||||
generate_pop(frame_info_array, frame_count);
|
||||
|
||||
// End read
|
||||
png_read_end(png_ptr, info_ptr);
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
||||
|
||||
// Close input stream
|
||||
close_patch_head_input_stream(env, patch_head_input_stream);
|
||||
destroy_patch_head_input_stream(env, &patch_head_input_stream);
|
||||
|
||||
png->partially = false;
|
||||
png->png_ptr = NULL;
|
||||
png->info_ptr = NULL;
|
||||
png->patch_head_input_stream = NULL;
|
||||
} else {
|
||||
png->partially = true;
|
||||
png->png_ptr = png_ptr;
|
||||
png->info_ptr = info_ptr;
|
||||
png->patch_head_input_stream = patch_head_input_stream;
|
||||
}
|
||||
|
||||
// Fill PNG
|
||||
png->width = width;
|
||||
png->height = height;
|
||||
png->is_opaque = is_opaque;
|
||||
png->buffer = buffer;
|
||||
png->apng = true;
|
||||
png->buffer_index = -1;
|
||||
png->frame_info_array = frame_info_array;
|
||||
png->frame_count = frame_count;
|
||||
png->backup = NULL;
|
||||
|
||||
// Render first frame
|
||||
PNG_advance(png);
|
||||
} else {
|
||||
read_image(png_ptr, buffer, width, height);
|
||||
|
||||
// End read
|
||||
png_read_end(png_ptr, info_ptr);
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
||||
|
||||
// Close input stream
|
||||
close_patch_head_input_stream(env, patch_head_input_stream);
|
||||
destroy_patch_head_input_stream(env, &patch_head_input_stream);
|
||||
|
||||
// Fill PNG
|
||||
png->width = width;
|
||||
png->height = height;
|
||||
png->buffer = buffer;
|
||||
png->apng = false;
|
||||
png->buffer_index = 0;
|
||||
png->frame_info_array = NULL;
|
||||
png->frame_count = 0;
|
||||
png->backup = NULL;
|
||||
png->partially = false;
|
||||
png->png_ptr = NULL;
|
||||
png->info_ptr = NULL;
|
||||
png->patch_head_input_stream = NULL;
|
||||
}
|
||||
|
||||
return png;
|
||||
}
|
||||
|
||||
bool PNG_complete(JNIEnv *env, PNG* png)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!png->partially) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (png->png_ptr == NULL || png->info_ptr == NULL || png->patch_head_input_stream == NULL) {
|
||||
LOGE(MSG("Some stuff is NULL"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read left frames
|
||||
for (i = 1; i < png->frame_count; read_frame(png->png_ptr, png->info_ptr, png->frame_info_array + i++));
|
||||
|
||||
// Generate pop
|
||||
generate_pop(png->frame_info_array, png->frame_count);
|
||||
|
||||
// End read
|
||||
png_read_end(png->png_ptr, png->info_ptr);
|
||||
png_destroy_read_struct(&png->png_ptr, &png->info_ptr, NULL);
|
||||
|
||||
// Close input stream
|
||||
close_patch_head_input_stream(env, png->patch_head_input_stream);
|
||||
destroy_patch_head_input_stream(env, &png->patch_head_input_stream);
|
||||
|
||||
// Clean up
|
||||
png->partially = false;
|
||||
png->png_ptr = NULL;
|
||||
png->info_ptr = NULL;
|
||||
png->patch_head_input_stream = NULL;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PNG_is_completed(PNG* png)
|
||||
{
|
||||
return !png->partially;
|
||||
}
|
||||
|
||||
void* PNG_get_pixels(PNG* png)
|
||||
{
|
||||
if (!png->partially && PNG_get_frame_count(png) == 1) {
|
||||
return png->buffer;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int PNG_get_width(PNG* png)
|
||||
{
|
||||
return png->width;
|
||||
}
|
||||
|
||||
int PNG_get_height(PNG* png)
|
||||
{
|
||||
return png->height;
|
||||
}
|
||||
|
||||
int PNG_get_byte_count(PNG* png)
|
||||
{
|
||||
int i;
|
||||
PNG_FRAME_INFO* frame_info;
|
||||
int size = 0;
|
||||
// Add buffer size
|
||||
if (png->buffer != NULL) {
|
||||
size += png->width * png->height * 4;
|
||||
}
|
||||
// Add backup size
|
||||
if (png->backup != NULL) {
|
||||
size += png->width * png->height * 4;
|
||||
}
|
||||
// Add frames size
|
||||
if (png->frame_info_array != NULL) {
|
||||
for (i = 0; i < png->frame_count; i++) {
|
||||
frame_info = png->frame_info_array + i;
|
||||
// Add frame buffer size
|
||||
if (frame_info->buffer != NULL) {
|
||||
size += frame_info->width * frame_info->height * 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
void PNG_render(PNG* png, int src_x, int src_y,
|
||||
void* dst, int dst_w, int dst_h, int dst_x, int dst_y,
|
||||
int width, int height, bool fill_blank, int default_color)
|
||||
{
|
||||
copy_pixels(png->buffer, png->width, png->height, src_x, src_y,
|
||||
dst, dst_w, dst_h, dst_x, dst_y,
|
||||
width, height, fill_blank, default_color);
|
||||
}
|
||||
|
||||
static void backup(PNG* png)
|
||||
{
|
||||
if (png->backup == NULL) {
|
||||
png->backup = (unsigned char*) malloc(png->width * png->height * 4);
|
||||
if (png->backup == NULL) {
|
||||
WTF_OM;
|
||||
return;
|
||||
}
|
||||
}
|
||||
memcpy(png->backup, png->buffer, png->width * png->height * 4);
|
||||
}
|
||||
|
||||
static void restore(PNG* png)
|
||||
{
|
||||
if (png->backup == NULL) {
|
||||
LOGE(MSG("Backup is NULL"));
|
||||
} else {
|
||||
memcpy(png->buffer, png->backup, png->width * png->height * 4);
|
||||
}
|
||||
}
|
||||
|
||||
static void switch_buffer_backup(PNG* png)
|
||||
{
|
||||
unsigned char* temp;
|
||||
|
||||
if (png->backup == NULL) {
|
||||
backup(png);
|
||||
} else {
|
||||
temp = png->buffer;
|
||||
png->buffer = png->backup;
|
||||
png->backup = temp;
|
||||
}
|
||||
}
|
||||
|
||||
static void blend_over(unsigned char* dp, const unsigned char* sp, size_t len)
|
||||
{
|
||||
unsigned int i;
|
||||
int u, v, al;
|
||||
|
||||
for (i = 0; i < len; i += 4, sp += 4, dp += 4) {
|
||||
if (sp[3] == 255) {
|
||||
memcpy(dp, sp, 4);
|
||||
} else if (sp[3] != 0) {
|
||||
if (dp[3] != 0) {
|
||||
u = sp[3] * 255;
|
||||
v = (255 - sp[3]) * dp[3];
|
||||
al = u + v;
|
||||
dp[0] = (unsigned char) ((sp[0] * u + dp[0] * v) / al);
|
||||
dp[1] = (unsigned char) ((sp[1] * u + dp[1] * v) / al);
|
||||
dp[2] = (unsigned char) ((sp[2] * u + dp[2] * v) / al);
|
||||
dp[3] = (unsigned char) (al / 255);
|
||||
} else {
|
||||
memcpy(dp, sp, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void blend(unsigned char* src, int src_width, int src_height, int offset_x, int offset_y,
|
||||
unsigned char* dst, int dst_width, int dst_height, bool blend_op_over)
|
||||
{
|
||||
int i;
|
||||
unsigned char* src_ptr;
|
||||
unsigned char* dst_ptr;
|
||||
size_t len;
|
||||
int copy_width = MIN(dst_width - offset_x, src_width);
|
||||
int copy_height = MIN(dst_height - offset_y, src_height);
|
||||
|
||||
for (i = 0; i < copy_height; i++) {
|
||||
src_ptr = src + (i * src_width * 4);
|
||||
dst_ptr = dst + (((offset_y + i) * dst_width + offset_x) * 4);
|
||||
len = (size_t) (copy_width * 4);
|
||||
|
||||
if (blend_op_over) {
|
||||
blend_over(dst_ptr, src_ptr, len);
|
||||
} else {
|
||||
memcpy(dst_ptr, src_ptr, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PNG_advance(PNG* png)
|
||||
{
|
||||
int index;
|
||||
|
||||
if (!png->apng) {
|
||||
LOGW(MSG("It is not apng, no need to advance"));
|
||||
return;
|
||||
}
|
||||
|
||||
index = (png->buffer_index + 1) % png->frame_count;
|
||||
if (index != 0 && png->partially) {
|
||||
LOGE(MSG("The png is only decoded partially. Only the first frame can be shown."));
|
||||
return;
|
||||
}
|
||||
|
||||
PNG_FRAME_INFO* frame_info = png->frame_info_array + index;
|
||||
|
||||
if (frame_info->dop == PNG_DISPOSE_OP_PREVIOUS && frame_info->pop == IMAGE_PNG_PREPARE_USE_BACKUP) {
|
||||
switch_buffer_backup(png);
|
||||
} else {
|
||||
// Backup
|
||||
if (frame_info->dop == PNG_DISPOSE_OP_PREVIOUS) {
|
||||
backup(png);
|
||||
}
|
||||
|
||||
// Prepare
|
||||
switch (frame_info->pop) {
|
||||
case IMAGE_PNG_PREPARE_NONE:
|
||||
// Do nothing
|
||||
break;
|
||||
default:
|
||||
case IMAGE_PNG_PREPARE_BACKGROUND:
|
||||
// Set transparent
|
||||
memset(png->buffer, '\0', png->width * png->height * 4);
|
||||
break;
|
||||
case IMAGE_PNG_PREPARE_USE_BACKUP:
|
||||
restore(png);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
blend(frame_info->buffer, frame_info->width, frame_info->height, frame_info->offset_x, frame_info->offset_y,
|
||||
png->buffer, png->width, png->height, frame_info->bop == PNG_BLEND_OP_OVER);
|
||||
|
||||
png->buffer_index = index;
|
||||
}
|
||||
|
||||
int PNG_get_delay(PNG* png)
|
||||
{
|
||||
if (png->apng) {
|
||||
return png->frame_info_array[png->buffer_index].delay;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int PNG_get_frame_count(PNG* png)
|
||||
{
|
||||
if (png->apng) {
|
||||
return png->frame_count;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
bool PNG_is_opaque(PNG* png)
|
||||
{
|
||||
return png->is_opaque;
|
||||
}
|
||||
|
||||
void PNG_recycle(JNIEnv *env, PNG* png)
|
||||
{
|
||||
if (png == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
free(png->buffer);
|
||||
png->buffer = NULL;
|
||||
|
||||
free_frame_info_array(png->frame_info_array, png->frame_count);
|
||||
png->frame_info_array = NULL;
|
||||
|
||||
free(png->backup);
|
||||
png->backup = NULL;
|
||||
|
||||
if (png->png_ptr != NULL && png->info_ptr != NULL) {
|
||||
png_destroy_read_struct(&png->png_ptr, &png->info_ptr, NULL);
|
||||
}
|
||||
png->png_ptr = NULL;
|
||||
png->info_ptr = NULL;
|
||||
|
||||
if (png->patch_head_input_stream != NULL) {
|
||||
close_patch_head_input_stream(env, png->patch_head_input_stream);
|
||||
destroy_patch_head_input_stream(env, &png->patch_head_input_stream);
|
||||
png->patch_head_input_stream = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // IMAGE_SUPPORT_PNG
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2015 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.
|
||||
*/
|
||||
|
||||
//
|
||||
// Created by Hippo on 12/27/2015.
|
||||
//
|
||||
|
||||
#ifndef IMAGE_IMAGE_PNG_H
|
||||
#define IMAGE_IMAGE_PNG_H
|
||||
|
||||
#include "config.h"
|
||||
#ifdef IMAGE_SUPPORT_PNG
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "png.h"
|
||||
#include "patch_head_input_stream.h"
|
||||
|
||||
#define IMAGE_PNG_DECODER_DESCRIPTION ("libpng " PNG_LIBPNG_VER_STRING)
|
||||
|
||||
#define IMAGE_PNG_MAGIC_NUMBER_0 0x89
|
||||
#define IMAGE_PNG_MAGIC_NUMBER_1 0x50
|
||||
|
||||
#define IMAGE_PNG_PREPARE_UNKNOWN 0x00
|
||||
#define IMAGE_PNG_PREPARE_NONE 0x01
|
||||
#define IMAGE_PNG_PREPARE_BACKGROUND 0x02
|
||||
#define IMAGE_PNG_PREPARE_USE_BACKUP 0x03
|
||||
|
||||
typedef struct {
|
||||
unsigned char* buffer;
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
unsigned int offset_x;
|
||||
unsigned int offset_y;
|
||||
unsigned int delay; // ms
|
||||
unsigned char dop;
|
||||
unsigned char bop;
|
||||
unsigned char pop;
|
||||
} PNG_FRAME_INFO;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
bool is_opaque;
|
||||
unsigned char* buffer;
|
||||
bool apng;
|
||||
int buffer_index;
|
||||
PNG_FRAME_INFO* frame_info_array;
|
||||
unsigned int frame_count;
|
||||
unsigned char* backup;
|
||||
bool partially;
|
||||
png_structp png_ptr;
|
||||
png_infop info_ptr;
|
||||
PatchHeadInputStream* patch_head_input_stream;
|
||||
} PNG;
|
||||
|
||||
void* PNG_decode(JNIEnv* env, PatchHeadInputStream* patch_head_input_stream, bool partially);
|
||||
bool PNG_complete(JNIEnv* env, PNG* png);
|
||||
bool PNG_is_completed(PNG* png);
|
||||
void* PNG_get_pixels(PNG* png);
|
||||
int PNG_get_width(PNG* png);
|
||||
int PNG_get_height(PNG* png);
|
||||
int PNG_get_byte_count(PNG* png);
|
||||
void PNG_render(PNG* png, int src_x, int src_y,
|
||||
void* dst, int dst_w, int dst_h, int dst_x, int dst_y,
|
||||
int width, int height, bool fill_blank, int default_color);
|
||||
void PNG_advance(PNG* png);
|
||||
int PNG_get_delay(PNG* png);
|
||||
int PNG_get_frame_count(PNG* png);
|
||||
bool PNG_is_opaque(PNG* png);
|
||||
void PNG_recycle(JNIEnv* env, PNG* png);
|
||||
|
||||
#endif // IMAGE_SUPPORT_PNG
|
||||
|
||||
#endif // IMAGE_IMAGE_PNG_H
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright 2015 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.
|
||||
*/
|
||||
|
||||
//
|
||||
// Created by Hippo on 12/27/2015.
|
||||
//
|
||||
|
||||
#include "image_utils.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define LITTLE_ENDIAN 0x00
|
||||
#define BIG_ENDIAN 0x01
|
||||
|
||||
static unsigned int endian = ~0U;
|
||||
|
||||
static unsigned int get_endian() {
|
||||
unsigned int x = 1;
|
||||
|
||||
if (endian == ~0U) {
|
||||
if (1 == ((char *)&x)[0]) {
|
||||
endian = LITTLE_ENDIAN;
|
||||
} else {
|
||||
endian = BIG_ENDIAN;
|
||||
}
|
||||
}
|
||||
|
||||
return endian;
|
||||
}
|
||||
|
||||
static int convert_color(int origin) {
|
||||
int result;
|
||||
unsigned char* orPtr = (unsigned char *) &origin;
|
||||
unsigned char* rePtr = (unsigned char *) &result;
|
||||
|
||||
if (get_endian() == BIG_ENDIAN) {
|
||||
rePtr[0] = orPtr[1];
|
||||
rePtr[1] = orPtr[2];
|
||||
rePtr[2] = orPtr[3];
|
||||
rePtr[3] = orPtr[0];
|
||||
} else {
|
||||
rePtr[0] = orPtr[2];
|
||||
rePtr[1] = orPtr[1];
|
||||
rePtr[2] = orPtr[0];
|
||||
rePtr[3] = orPtr[3];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void memset_int(int* dst, int val, size_t size) {
|
||||
int* maxPtr = dst + size;
|
||||
int* ptr = dst;
|
||||
while(ptr < maxPtr)
|
||||
*ptr++ = val;
|
||||
}
|
||||
|
||||
bool copy_pixels_internal(const void* src, int src_w, int src_h, int src_x, int src_y,
|
||||
void* dst, int dst_w, int dst_h, int dst_x, int dst_y,
|
||||
int width, int height, bool fill_blank, int color)
|
||||
{
|
||||
int left;
|
||||
int line;
|
||||
size_t line_stride;
|
||||
int src_stride;
|
||||
int src_pos;
|
||||
int dst_pos;
|
||||
size_t dst_blank_length;
|
||||
|
||||
// Sanitize
|
||||
if (src_x < 0) {
|
||||
width -= src_x;
|
||||
dst_x -= src_x;
|
||||
src_x = 0;
|
||||
}
|
||||
if (dst_x < 0) {
|
||||
width -= dst_x;
|
||||
src_x -= dst_x;
|
||||
dst_x = 0;
|
||||
}
|
||||
if (width <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (src_y < 0) {
|
||||
height -= src_y;
|
||||
dst_y -= src_y;
|
||||
src_y = 0;
|
||||
}
|
||||
if (dst_y < 0) {
|
||||
height -= dst_y;
|
||||
src_y -= dst_y;
|
||||
dst_y = 0;
|
||||
}
|
||||
if (height <= 0) {
|
||||
return false;
|
||||
}
|
||||
left = src_x + width - src_w;
|
||||
if (left > 0) {
|
||||
width -= left;
|
||||
}
|
||||
left = dst_x + width - dst_w;
|
||||
if (left > 0) {
|
||||
width -= left;
|
||||
}
|
||||
if (width <= 0) {
|
||||
return false;
|
||||
}
|
||||
left = src_y + height - src_h;
|
||||
if (left > 0) {
|
||||
height -= left;
|
||||
}
|
||||
left = dst_y + height - dst_h;
|
||||
if (left > 0) {
|
||||
height -= left;
|
||||
}
|
||||
if (height <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Init
|
||||
line_stride = (size_t) (width * 4);
|
||||
src_stride = src_w * 4;
|
||||
src_pos = src_y * src_stride + src_x * 4;
|
||||
dst_pos = 0;
|
||||
|
||||
dst_blank_length = (size_t) (dst_y * dst_w + dst_x) * 4;
|
||||
// Fill start blank if necessary
|
||||
if (fill_blank) {
|
||||
memset_int((int *) (dst + dst_pos), color, dst_blank_length / 4);
|
||||
}
|
||||
|
||||
// First line
|
||||
dst_pos += dst_blank_length;
|
||||
memcpy(dst + dst_pos, src + src_pos, line_stride);
|
||||
dst_pos += line_stride;
|
||||
src_pos += src_stride;
|
||||
|
||||
// Other lines
|
||||
dst_blank_length = (size_t) ((dst_w - width) * 4);
|
||||
for (line = 1; line < height; line++) {
|
||||
if (fill_blank) {
|
||||
memset_int((int *) (dst + dst_pos), color, dst_blank_length / 4);
|
||||
}
|
||||
dst_pos += dst_blank_length;
|
||||
memcpy(dst + dst_pos, src + src_pos, line_stride);
|
||||
dst_pos += line_stride;
|
||||
src_pos += src_stride;
|
||||
}
|
||||
|
||||
// Fill left blank if necessary
|
||||
if (fill_blank) {
|
||||
memset_int((int *) (dst + dst_pos), color, (size_t) (dst_w * dst_h * 4 - dst_pos) / 4);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void copy_pixels(const void* src, int src_w, int src_h, int src_x, int src_y,
|
||||
void* dst, int dst_w, int dst_h, int dst_x, int dst_y,
|
||||
int width, int height, bool fill_blank, int default_color)
|
||||
{
|
||||
int color = 0;
|
||||
if (fill_blank) {
|
||||
color = convert_color(default_color);
|
||||
}
|
||||
|
||||
if (!copy_pixels_internal(src, src_w, src_h, src_x, src_y, dst, dst_w, dst_h, dst_x,
|
||||
dst_y, width, height, fill_blank, color) && fill_blank) {
|
||||
memset_int(dst, color, (size_t) (dst_w * dst_h));
|
||||
}
|
||||
}
|
||||
@@ -26,8 +26,8 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
void copy_pixels(const void *src, int src_w, int src_h, int src_x, int src_y,
|
||||
void *dst, int dst_w, int dst_h, int dst_x, int dst_y,
|
||||
int width, int height, bool fill_blank, int default_color);
|
||||
void copy_pixels(const void* src, int src_w, int src_h, int src_x, int src_y,
|
||||
void* dst, int dst_w, int dst_h, int dst_x, int dst_y,
|
||||
int width, int height, bool fill_blank, int default_color);
|
||||
|
||||
#endif //IMAGE_IMAGE_UTILS_H
|
||||
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
* Copyright 2015 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.
|
||||
*/
|
||||
|
||||
//
|
||||
// Created by Hippo on 12/27/2015.
|
||||
//
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <android/bitmap.h>
|
||||
#include <GLES2/gl2.h>
|
||||
|
||||
#include "java_wrapper.h"
|
||||
#include "input_stream.h"
|
||||
#include "image.h"
|
||||
#include "../log.h"
|
||||
|
||||
static JavaVM* jvm;
|
||||
|
||||
static void* tile_buffer;
|
||||
|
||||
JNIEnv *obtain_env(bool *attach)
|
||||
{
|
||||
JNIEnv *env;
|
||||
switch ((*jvm)->GetEnv(jvm, (void**) &env, JNI_VERSION_1_6)) {
|
||||
case JNI_EDETACHED:
|
||||
if ((*jvm)->AttachCurrentThread(jvm, &env, NULL) == JNI_OK) {
|
||||
*attach = true;
|
||||
return env;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
case JNI_OK:
|
||||
*attach = false;
|
||||
return env;
|
||||
default:
|
||||
case JNI_EVERSION:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void release_env()
|
||||
{
|
||||
(*jvm)->DetachCurrentThread(jvm);
|
||||
}
|
||||
|
||||
jobject create_image_object(JNIEnv* env, void* ptr, int format, int width, int height)
|
||||
{
|
||||
jclass image_clazz;
|
||||
jmethodID constructor;
|
||||
|
||||
image_clazz = (*env)->FindClass(env, "com/hippo/image/Image");
|
||||
constructor = (*env)->GetMethodID(env, image_clazz, "<init>", "(JIII)V");
|
||||
if (constructor == 0) {
|
||||
LOGE(MSG("Can't find Image object constructor"));
|
||||
return NULL;
|
||||
} else {
|
||||
return (*env)->NewObject(env, image_clazz, constructor,
|
||||
(jlong) (uintptr_t) ptr, (jint) format, (jint) width, (jint) height);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_com_hippo_image_Image_nativeDecode(JNIEnv* env,
|
||||
jclass clazz, jobject is, jboolean partially)
|
||||
{
|
||||
InputStream* input_stream;
|
||||
int format;
|
||||
void* image;
|
||||
jobject image_object;
|
||||
|
||||
input_stream = create_input_stream(env, is);
|
||||
if (input_stream == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
image = decode(env, input_stream, partially, &format);
|
||||
if (image == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
image_object = create_image_object(env, image, format,
|
||||
get_width(image, format), get_height(image, format));
|
||||
if (image_object == NULL) {
|
||||
recycle(env, image, format);
|
||||
return NULL;
|
||||
} else {
|
||||
return image_object;
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_com_hippo_image_Image_nativeCreate(JNIEnv* env,
|
||||
jclass clazz, jobject bitmap)
|
||||
{
|
||||
#ifdef IMAGE_SUPPORT_PLAIN
|
||||
AndroidBitmapInfo info;
|
||||
void *pixels = NULL;
|
||||
void* image = NULL;
|
||||
jobject image_object;
|
||||
|
||||
AndroidBitmap_getInfo(env, bitmap, &info);
|
||||
AndroidBitmap_lockPixels(env, bitmap, &pixels);
|
||||
if (pixels == NULL) {
|
||||
LOGE(MSG("Can't lock bitmap pixels"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
image = create(info.width, info.height, pixels);
|
||||
|
||||
AndroidBitmap_unlockPixels(env, bitmap);
|
||||
|
||||
if (image == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
image_object = create_image_object(env, image, IMAGE_FORMAT_PLAIN,
|
||||
info.width, info.height);
|
||||
if (image_object == NULL) {
|
||||
recycle(env, image, IMAGE_FORMAT_PLAIN);
|
||||
return NULL;
|
||||
} else {
|
||||
return image_object;
|
||||
}
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_hippo_image_Image_nativeComplete(JNIEnv* env,
|
||||
jclass clazz, jlong ptr, jint format)
|
||||
{
|
||||
return (jboolean) complete(env, (void*) (intptr_t) ptr, format);
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_hippo_image_Image_nativeIsCompleted(JNIEnv* env,
|
||||
jclass clazz, jlong ptr, jint format)
|
||||
{
|
||||
return (jboolean) is_completed((void*) (intptr_t) ptr, format);
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_hippo_image_Image_nativeGetByteCount(JNIEnv* env,
|
||||
jclass clazz, jlong ptr, jint format)
|
||||
{
|
||||
return (jint) get_byte_count((void*) (intptr_t) ptr, format);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_image_Image_nativeRender(JNIEnv* env,
|
||||
jclass clazz, jlong ptr, jint format,
|
||||
jint src_x, jint src_y, jobject dst, jint dst_x, jint dst_y,
|
||||
jint width, jint height, jboolean fill_blank, jint default_color)
|
||||
{
|
||||
AndroidBitmapInfo info;
|
||||
void *pixels = NULL;
|
||||
|
||||
AndroidBitmap_getInfo(env, dst, &info);
|
||||
AndroidBitmap_lockPixels(env, dst, &pixels);
|
||||
if (pixels == NULL) {
|
||||
LOGE(MSG("Can't lock bitmap pixels"));
|
||||
return;
|
||||
}
|
||||
|
||||
render((void*) (intptr_t) ptr, format, src_x, src_y,
|
||||
pixels, info.width, info.height, dst_x, dst_y,
|
||||
width, height, fill_blank, default_color);
|
||||
|
||||
AndroidBitmap_unlockPixels(env, dst);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_image_Image_nativeTexImage(JNIEnv* env,
|
||||
jclass clazz, jlong ptr, jint format, jboolean init,
|
||||
jint src_x, jint src_y, jint width, jint height)
|
||||
{
|
||||
// Check tile_buffer NULL
|
||||
if (NULL == tile_buffer) {
|
||||
return;
|
||||
}
|
||||
// Check render size
|
||||
if (width * height > IMAGE_TILE_MAX_SIZE) {
|
||||
return;
|
||||
}
|
||||
|
||||
render((void*) (intptr_t) ptr, format, src_x, src_y,
|
||||
tile_buffer, width, height, 0, 0,
|
||||
width, height, false, 0);
|
||||
|
||||
if (init) {
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height,
|
||||
0, GL_RGBA, GL_UNSIGNED_BYTE, tile_buffer);
|
||||
} else {
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height,
|
||||
GL_RGBA, GL_UNSIGNED_BYTE, tile_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_image_Image_nativeAdvance(JNIEnv* env,
|
||||
jclass clazz, jlong ptr, jint format)
|
||||
{
|
||||
advance((void*) (intptr_t) ptr, format);
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_hippo_image_Image_nativeGetDelay(JNIEnv* env,
|
||||
jclass clazz, jlong ptr, jint format)
|
||||
{
|
||||
return (jint) get_delay((void*) (intptr_t) ptr, format);
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_hippo_image_Image_nativeFrameCount(JNIEnv* env,
|
||||
jclass clazz, jlong ptr, jint format)
|
||||
{
|
||||
return (jint) get_frame_count((void*) (intptr_t) ptr, format);
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_hippo_image_Image_nativeIsOpaque(JNIEnv* env,
|
||||
jclass clazz, jlong ptr, jint format)
|
||||
{
|
||||
return (jboolean) is_opaque((void*) (intptr_t) ptr, format);
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_hippo_image_Image_nativeIsGray(JNIEnv* env,
|
||||
jclass clazz, jlong ptr, jint format, jint error)
|
||||
{
|
||||
return (jboolean) is_gray((void*) (intptr_t) ptr, format, error);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_image_Image_nativeClahe(JNIEnv* env,
|
||||
jclass clazz, jlong ptr, jint format, jboolean to_gray)
|
||||
{
|
||||
clahe((void*) (intptr_t) ptr, format, to_gray);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_image_Image_nativeRecycle(JNIEnv* env,
|
||||
jclass clazz, jlong ptr, jint format)
|
||||
{
|
||||
recycle(env, (void*) (intptr_t) ptr, format);
|
||||
}
|
||||
|
||||
JNIEXPORT jintArray JNICALL
|
||||
Java_com_hippo_image_Image_nativeGetSupportedImageFormats(
|
||||
JNIEnv *env, jclass clazz)
|
||||
{
|
||||
int formats[IMAGE_MAX_SUPPORTED_FORMAT_COUNT];
|
||||
int count = get_supported_formats(formats);
|
||||
jintArray array = (*env)->NewIntArray(env, count);
|
||||
if (array == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
(*env)->SetIntArrayRegion(env, array, 0, count, formats);
|
||||
return array;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_hippo_image_Image_nativeGetDecoderDescription(
|
||||
JNIEnv *env, jclass clazz, jint format)
|
||||
{
|
||||
const char *description = get_decoder_description(format);
|
||||
if (description == NULL) {
|
||||
return NULL;
|
||||
} else {
|
||||
return (*env)->NewStringUTF(env, description);
|
||||
}
|
||||
}
|
||||
|
||||
bool image_onLoad(JavaVM *vm)
|
||||
{
|
||||
jvm = vm;
|
||||
|
||||
tile_buffer = malloc(IMAGE_TILE_MAX_SIZE * 4);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
JNI_OnUnload(JavaVM *vm, void *reserved)
|
||||
{
|
||||
free(tile_buffer);
|
||||
tile_buffer = NULL;
|
||||
}
|
||||