Compare commits

..
14 Commits
Author SHA1 Message Date
tarsin a7f7bf1316 Release 1.8
CI / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-07-25 10:20:16 +08:00
tarsin a72fca3dfb Fix a lag 2022-07-25 10:16:54 +08:00
tarsin c5e7a6e8f9 Code clean 2022-07-25 10:02:34 +08:00
tarsin 80a20f25b9 Fix animated 2022-07-25 09:28:40 +08:00
tarsin 500163fe1a Merge pull request #132 from Ehviewer-Overhauled/master
Pull master
2022-07-25 07:54:04 +08:00
tarsin 50c6111773 Imagedecoder do not support anon pipe, drop it and use memarea 2022-07-24 23:32:03 +08:00
tarsin 3a05649c71 Code clean 2022-07-24 22:34:19 +08:00
tarsin 9d25376e1f Refactor 2022-07-24 21:41:53 +08:00
tarsin 89935b5639 Hard cast 2022-07-24 20:32:39 +08:00
tarsin e095627aee Refactor 2022-07-24 20:20:04 +08:00
tarsin b61e46e956 Clean up 2022-07-24 19:49:34 +08:00
tarsin 76459c5dd2 Initial stupid migrate to ImageDecoder 2022-07-24 19:18:54 +08:00
tarsin 7ad67a3dbd Minsdk 31 2022-07-24 18:24:24 +08:00
tarsin 577b49ac19 Drop image decode native libs 2022-07-24 18:17:37 +08:00
127 changed files with 1678 additions and 6108 deletions

No files matched your search

+1 -1
View File
@@ -57,7 +57,7 @@ body:
id: ci
attributes:
label: EhViewer CI 版本 / EhViewer CI Version
description: 直接将对应 GitHub Action CI 下载页面网址填入即可 / Simply drop GitHub Action CI download page url here
description: 直接将对应 CI 下载页面网址填入即可 / Simply drop CI download page url here
placeholder: https://github.com/Ehviewer-Overhauled/Ehviewer/actions/runs/XXXXXXXXXX
validations:
required: true
-1
View File
@@ -1,4 +1,3 @@
blank_issues_enabled: false
contact_links:
- name: 所有其他问题 / All other questions
url: https://github.com/Ehviewer-Overhauled/Ehviewer/discussions
+1 -1
View File
@@ -56,7 +56,7 @@ body:
id: ci
attributes:
label: EhViewer CI 版本 / EhViewer CI Version
description: 直接将对应 GitHub Action CI 下载页面网址填入即可 / Simply drop GitHub Action CI download page url here
description: 直接将对应 CI 下载页面网址填入即可 / Simply drop CI download page url here
placeholder: https://github.com/Ehviewer-Overhauled/Ehviewer/actions/runs/XXXXXXXXXX
validations:
required: true
+7 -10
View File
@@ -1,11 +1,6 @@
name: CI
on:
push:
branches:
- '*'
tags-ignore:
- "*"
on: [push]
jobs:
linux:
@@ -14,8 +9,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: Setup Java 11
uses: actions/setup-java@v3
@@ -23,14 +16,18 @@ 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
with:
arguments: assembleRelease
- run: |
git submodule update --init --recursive
gradle assembleRelease
- name: Update ARM64
uses: actions/upload-artifact@v3
+6 -4
View File
@@ -9,8 +9,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: Setup Java 11
uses: actions/setup-java@v3
@@ -18,14 +16,18 @@ 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
with:
arguments: assembleRelease
- run: |
git submodule update --init --recursive
gradle assembleRelease
- name: Update ARM64
uses: actions/upload-artifact@v3
+11 -18
View File
@@ -12,8 +12,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: Setup Java 11
uses: actions/setup-java@v3
@@ -21,31 +19,26 @@ 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
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
- run: |
git submodule update --init --recursive
gradle assembleRelease
- name: Releases
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v0.1.14
with:
body: Bump Version
discussion_category_name: Announcements
files: |
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
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
-9
View File
@@ -1,12 +1,3 @@
[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
+14 -44
View File
@@ -1,17 +1,5 @@
<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">
<img src="art/launcher_icon-web.png" width="200">
<br>EhViewer<br>
</h1>
@@ -19,7 +7,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/1.7.x.x/LICENSE">
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/master/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">
@@ -33,42 +21,22 @@
</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
<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>
[Github 下载](https://github.com/Ehviewer-Overhauled/Ehviewer/releases)
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
[CI 版本下载(需登录)](https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml)
# Screenshot
@@ -77,11 +45,12 @@ App is divided into two versions: 1.7 and 1.8 . Version 1.8 support Android 12 a
# Thanks
本项目受到了诸多开源项目的帮助
Here is the libraries
- [AOSP&AndroidX](http://source.android.com/)
- [FullDraggableDrawer](https://github.com/PureWriter/FullDraggableDrawer)
- [GnuTLS](https://gnutls.org/)
- [AOSP](http://source.android.com/)
- [android-advancedrecyclerview](https://github.com/h6ah4i/android-advancedrecyclerview)
- [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/)
@@ -93,6 +62,7 @@ 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/)
+15 -19
View File
@@ -18,7 +18,6 @@ android {
enable = true
reset()
include 'arm64-v8a', 'x86_64', 'armeabi-v7a', 'x86'
universalApk = true
}
}
@@ -33,10 +32,10 @@ android {
defaultConfig {
applicationId "moe.tarsin.ehviewer"
minSdkVersion 26
minSdkVersion 31
targetSdkVersion 33
versionCode 114536
versionName "1.7.30.0"
versionCode 180000
versionName "1.8.0.0"
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
}
@@ -93,13 +92,13 @@ tasks.withType(JavaCompile) {
}
dependencies {
def activity_version = "1.6.0-beta01"
def activity_version = "1.6.0-alpha05"
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-beta01"
implementation "androidx.appcompat:appcompat:1.6.0-alpha05"
def arch_core_version = "2.1.0"
implementation "androidx.arch.core:core-common:$arch_core_version"
@@ -108,14 +107,14 @@ dependencies {
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha04"
implementation "androidx.browser:browser:1.4.0"
def collection_version = "1.3.0-alpha02"
def collection_version = "1.3.0-alpha01"
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-beta01"
def core_version = "1.9.0-alpha05"
implementation "androidx.core:core:$core_version"
implementation "androidx.core:core-ktx:$core_version"
@@ -123,12 +122,12 @@ dependencies {
implementation "androidx.documentfile:documentfile:1.1.0-alpha01"
implementation "androidx.dynamicanimation:dynamicanimation:1.1.0-alpha03"
def fragment_version = "1.5.2"
def fragment_version = "1.5.0"
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-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata:$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"
@@ -137,7 +136,7 @@ dependencies {
implementation "androidx.loader:loader:1.1.0"
implementation 'androidx.preference:preference:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.3.0-beta02'
implementation 'androidx.recyclerview:recyclerview:1.3.0-beta01'
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"
@@ -145,22 +144,19 @@ 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-rc01'
implementation 'androidx.webkit:webkit:1.5.0-beta01'
implementation 'com.drakeet.drawer:drawer:1.0.3'
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 '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 '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,6 +16,8 @@
package com.hippo.ehviewer;
import android.os.ParcelFileDescriptor;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -23,7 +25,9 @@ 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> {
@@ -34,7 +38,8 @@ public class ImageBitmapHelper implements ValueHelper<ImageBitmap> {
public ImageBitmap decode(@NonNull InputStreamPipe isPipe) {
try {
isPipe.obtain();
return ImageBitmap.decode(isPipe.open());
FileInputStream is = (FileInputStream) isPipe.open();
return ImageBitmap.decode(is.getFD());
} catch (OutOfMemoryError | IOException e) {
return null;
} finally {
@@ -100,11 +100,6 @@ 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";
@@ -1173,14 +1168,4 @@ 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,6 +34,9 @@ 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;
@@ -50,7 +53,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, InputStream> streams = new LinkedHashMap<>();
private final LinkedHashMap<Integer, Long> streams = new LinkedHashMap<>();
private final AtomicInteger decodingIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX);
private Thread archiveThread;
private Thread decodeThread;
@@ -250,18 +253,18 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
continue;
}
Pipe pipe = new Pipe();
synchronized (streams) {
if (streams.get(index) != null) {
continue;
}
streams.put(index, pipe.getInputStream());
streams.notify();
}
archiveAccessor.extracttoOutputStream(index, pipe.getOutputFd());
pipe.closeOutputFd();
long addr = archiveAccessor.extracttoOutputStream(index);
synchronized (streams) {
streams.put(index, addr);
streams.notify();
}
}
}
}
@@ -271,7 +274,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
public void run() {
while (!Thread.currentThread().isInterrupted()) {
int index;
InputStream stream;
Long addr;
synchronized (streams) {
if (streams.isEmpty()) {
try {
@@ -283,16 +286,16 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
continue;
}
Iterator<Map.Entry<Integer, InputStream>> iterator = streams.entrySet().iterator();
Map.Entry<Integer, InputStream> entry = iterator.next();
Iterator<Map.Entry<Integer, Long>> iterator = streams.entrySet().iterator();
Map.Entry<Integer, Long> entry = iterator.next();
iterator.remove();
index = entry.getKey();
stream = entry.getValue();
addr = entry.getValue();
decodingIndex.lazySet(index);
}
try {
Image image = Image.decode(stream, true);
Image image = Image.decodeAddr(addr, true);
if (image != null) {
notifyPageSucceed(index, image);
} else {
@@ -34,6 +34,7 @@ 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;
@@ -258,7 +259,7 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
InputStream is = null;
try {
is = files[index].openInputStream();
Image image = Image.decode(is, true);
Image image = Image.decode(((FileInputStream)is).getFD(), true);
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
if (image != null) {
notifyPageSucceed(index, image);
@@ -1,52 +0,0 @@
/*
* 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,7 +44,6 @@ 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,6 +66,7 @@ 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;
@@ -1096,33 +1097,6 @@ 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;
@@ -1693,7 +1667,7 @@ public final class SpiderQueen implements Runnable {
pipe.obtain();
try {
is = new AutoCloseInputStream(pipe, pipe.open());
is = pipe.open();
} catch (IOException e) {
// Can't open pipe
error = GetText.getString(R.string.error_reading_failed);
@@ -1703,7 +1677,18 @@ public final class SpiderQueen implements Runnable {
}
if (is != null) {
image = Image.decode(is, true);
try {
image = Image.decode(((FileInputStream)is).getFD(), true);
} catch (IOException e) {
e.printStackTrace();
image = null;
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (image == null) {
error = GetText.getString(R.string.error_decoding_failed);
}
@@ -394,22 +394,17 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
}
switch (event.getAction()) {
case MotionEvent.ACTION_SCROLL:
float scroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL) * 300;
if (scroll < 0.0f) {
if (event.getAxisValue(MotionEvent.AXIS_VSCROLL) < 0.0f) {
if (mLayoutMode == GalleryView.LAYOUT_RIGHT_TO_LEFT) {
mGalleryView.pageLeft();
} else if (mLayoutMode == GalleryView.LAYOUT_LEFT_TO_RIGHT){
} else {
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 if (mLayoutMode == GalleryView.LAYOUT_LEFT_TO_RIGHT) {
} else {
mGalleryView.pageLeft();
} else if (mLayoutMode == GalleryView.LAYOUT_TOP_TO_BOTTOM) {
mGalleryView.onScroll(0, -scroll, 0, -scroll, 0, -scroll);
}
}
}
@@ -307,6 +307,7 @@ 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);
@@ -0,0 +1,68 @@
/*
* 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);
}
}
@@ -1,64 +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.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,7 +38,6 @@ 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);
@@ -47,7 +46,6 @@ 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);
@@ -72,10 +70,6 @@ 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,6 +56,7 @@ 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();
@@ -117,6 +118,10 @@ public abstract class BaseScene extends SceneFragment {
return true;
}
public boolean needWhiteStatusBar() {
return true;
}
public int getNavCheckedItem() {
return 0;
}
@@ -171,6 +176,14 @@ 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() {
}
@@ -200,11 +213,7 @@ public abstract class BaseScene extends SceneFragment {
// Hide soft ime
hideSoftInput();
insetsController = getInsetsController();
if (insetsController != null) {
insetsController.setAppearanceLightStatusBars((requireActivity().getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_YES) <= 0);
}
setLightStatusBar(needWhiteStatusBar);
}
@Nullable
@@ -22,17 +22,21 @@ 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;
@@ -41,18 +45,26 @@ 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;
@@ -73,6 +85,7 @@ 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;
@@ -86,6 +99,7 @@ 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;
@@ -376,9 +390,90 @@ 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);
@@ -544,13 +639,20 @@ 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);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new DownloadLabelItemTouchHelperCallback());
itemTouchHelper.attachToRecyclerView(recyclerView);
recyclerView.setAdapter(mLabelAdapter);
final GeneralItemAnimator animator = new DraggableItemAnimator();
recyclerView.setItemAnimator(animator);
recyclerView.setAdapter(dragDropManager.createWrappedAdapter(mLabelAdapter));
dragDropManager.attachRecyclerView(recyclerView);
return view;
}
@@ -873,7 +975,7 @@ public class DownloadsScene extends ToolbarScene
holder.speed.setText(FileUtils.humanReadableByteCount(speed, false) + "/S");
}
private static class DownloadLabelHolder extends RecyclerView.ViewHolder {
private static class DownloadLabelHolder extends AbstractDraggableItemViewHolder {
private final TextView label;
private final ImageView option;
@@ -952,7 +1054,7 @@ public class DownloadsScene extends ToolbarScene
}
}
private class DownloadLabelAdapter extends RecyclerView.Adapter<DownloadLabelHolder> {
private class DownloadLabelAdapter extends RecyclerView.Adapter<DownloadLabelHolder> implements DraggableItemAdapter<DownloadLabelHolder> {
private final LayoutInflater mInflater;
@@ -1006,15 +1108,36 @@ public class DownloadsScene extends ToolbarScene
});
if (position > 0) {
holder.option.setVisibility(View.VISIBLE);
holder.option.setOnClickListener(v -> {
holder.itemView.setOnLongClickListener(v -> {
if (context != null) {
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);
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;
});
}
return true;
});
} else {
holder.option.setVisibility(View.GONE);
@@ -1031,6 +1154,43 @@ 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 {
@@ -1368,43 +1528,4 @@ 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,6 +45,8 @@ 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;
@@ -309,9 +311,40 @@ 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,6 +559,12 @@ 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);
@@ -662,6 +668,11 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
return view;
}
@Override
public boolean needWhiteStatusBar() {
return false;
}
@Override
public void onDestroyView() {
super.onDestroyView();
@@ -830,16 +841,19 @@ 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
@@ -848,6 +862,7 @@ 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,10 +18,13 @@ 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;
@@ -30,8 +33,10 @@ 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;
@@ -45,17 +50,25 @@ 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;
@@ -98,6 +111,7 @@ 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;
@@ -620,9 +634,40 @@ 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();
@@ -741,13 +786,19 @@ 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);
recyclerView.setAdapter(qsDrawerAdapter);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new GalleryListQSItemTouchHelperCallback(qsDrawerAdapter));
itemTouchHelper.attachToRecyclerView(recyclerView);
final GeneralItemAnimator animator = new DraggableItemAnimator();
recyclerView.setItemAnimator(animator);
recyclerView.setAdapter(dragDropManager.createWrappedAdapter(qsDrawerAdapter));
dragDropManager.attachRecyclerView(recyclerView);
mQuickSearchList = EhDB.getAllQuickSearch();
tip.setText(R.string.quick_search_tip);
if (mIsTopList) {
@@ -1474,7 +1525,7 @@ public final class GalleryListScene extends BaseScene
}
}
private static class QsDrawerHolder extends RecyclerView.ViewHolder {
private static class QsDrawerHolder extends AbstractDraggableItemViewHolder {
private final TextView key;
private final ImageView option;
@@ -1519,7 +1570,7 @@ public final class GalleryListScene extends BaseScene
}
}
private class QsDrawerAdapter extends RecyclerView.Adapter<QsDrawerHolder> {
private class QsDrawerAdapter extends RecyclerView.Adapter<QsDrawerHolder> implements DraggableItemAdapter<QsDrawerHolder> {
private final LayoutInflater mInflater;
@@ -1550,6 +1601,34 @@ 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};
@@ -1585,6 +1664,44 @@ 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 {
@@ -1769,47 +1886,4 @@ 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,11 +36,20 @@ 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;
@@ -197,9 +206,17 @@ 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()));
@@ -212,8 +229,9 @@ 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);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new HistoryItemTouchHelperCallback());
itemTouchHelper.attachToRecyclerView(mRecyclerView);
guardManager.attachRecyclerView(mRecyclerView);
swipeManager.attachRecyclerView(mRecyclerView);
mFastScroller.attachToRecyclerView(mRecyclerView);
HandlerDrawable handlerDrawable = new HandlerDrawable();
handlerDrawable.setColor(ResourcesKt.resolveColor(getTheme(), com.google.android.material.R.attr.colorPrimary));
@@ -484,7 +502,7 @@ public class HistoryScene extends ToolbarScene {
}
}
private static class HistoryHolder extends RecyclerView.ViewHolder {
private static class HistoryHolder extends AbstractSwipeableItemViewHolder {
public final View card;
public final LoadImageView thumb;
@@ -511,6 +529,12 @@ 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 {
@@ -546,7 +570,8 @@ public class HistoryScene extends ToolbarScene {
}
}
private class HistoryAdapter extends RecyclerView.Adapter<HistoryHolder> {
private class HistoryAdapter extends RecyclerView.Adapter<HistoryHolder>
implements SwipeableItemAdapter<HistoryHolder> {
private final LayoutInflater mInflater;
private final int mListThumbWidth;
@@ -624,30 +649,55 @@ 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 HistoryItemTouchHelperCallback extends ItemTouchHelper.Callback {
private class SwipeResultActionClear extends SwipeResultActionRemoveItem {
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
return makeMovementFlags(0, ItemTouchHelper.LEFT);
private final int mPosition;
protected SwipeResultActionClear(int position) {
mPosition = position;
}
@Override
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) {
protected void onPerformAction() {
super.onPerformAction();
if (null == mLazyList || null == mAdapter) {
return;
}
HistoryInfo info = mLazyList.get(mPosition);
EhDB.deleteHistoryInfo(info);
updateLazyList();
mAdapter.notifyItemRemoved(mPosition);
mAdapter.notifyDataSetChanged();
updateView(true);
}
}
@@ -87,6 +87,12 @@ public abstract class ToolbarScene extends BaseScene {
}
}
@Override
public boolean needWhiteStatusBar() {
return false;
}
public int getMenuResId() {
return 0;
}
@@ -53,6 +53,11 @@ 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) {
@@ -1,13 +0,0 @@
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_GIF && value.obtain()) {
if (value.getFormat() != Image.FORMAT_ANIMATED && 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_GIF) {
if (value.getFormat() == Image.FORMAT_ANIMATED) {
size *= 5;
}
return size;
@@ -153,12 +153,6 @@ 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() {
@@ -239,7 +233,7 @@ public class ImageTexture implements Texture, Animatable {
}
boolean end = mReleased.get() || mImage.isImageRecycled() || mNeedRelease.get() ||
(mImage.isCompleted() && mImage.getFrameCount() <= 1) || mRunning.get();
(mImage.getFormat() == 0) || mRunning.get();
synchronized (mImage) {
mImageBusy = false;
@@ -597,25 +591,11 @@ 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() || frameCount <= 1) {
if (mNeedRelease.get() || mImage.getFormat() == 0) {
mAnimateRunnable = null;
return;
}
@@ -122,22 +122,6 @@ 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)
*/
@@ -168,13 +152,6 @@ public class ImageWrapper {
return mImage.getDelay();
}
/**
* @see Image#getFrameCount()
*/
public int getFrameCount() {
return mImage.getFrameCount();
}
/**
* @see Image#isOpaque()
*/
@@ -21,49 +21,18 @@
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<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>
<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>
</vector>
@@ -42,7 +42,7 @@
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:padding="@dimen/keyline_margin"
app:srcCompat="@drawable/ic_launcher_foreground" />
app:srcCompat="@mipmap/ic_launcher" />
</LinearLayout>
@@ -77,11 +77,10 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.hippo.ehviewer.widget.FixedThumb
<com.hippo.widget.LoadImageView
android:id="@+id/thumb"
android:layout_width="@dimen/gallery_detail_thumb_width"
android:layout_height="@dimen/gallery_detail_thumb_height"
app:alwaysCutAndScale="true"/>
android:layout_height="@dimen/gallery_detail_thumb_height" />
</com.google.android.material.card.MaterialCardView>
+3 -3
View File
@@ -29,10 +29,10 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.hippo.ehviewer.widget.ResizeableFixedThumb
<com.hippo.ehviewer.widget.FixedThumb
android:id="@+id/thumb"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_width="@dimen/gallery_list_thumb_width"
android:layout_height="@dimen/gallery_list_thumb_height"
app:alwaysCutAndScale="true"
app:retryType="longClick" />
@@ -15,7 +15,6 @@
<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"
@@ -32,15 +31,15 @@
<com.google.android.material.card.MaterialCardView
android:id="@+id/thumb_card"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:layout_width="@dimen/gallery_list_thumb_width"
android:layout_height="@dimen/gallery_list_thumb_height">
<com.hippo.ehviewer.widget.ResizeableFixedThumb
<com.hippo.ehviewer.widget.FixedThumb
android:id="@+id/thumb"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:alwaysCutAndScale="true"
app:retryType="click"/>
app:retryType="click" />
</com.google.android.material.card.MaterialCardView>
+1 -1
View File
@@ -38,7 +38,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:padding="@dimen/keyline_margin"
app:srcCompat="@drawable/ic_launcher_foreground" />
app:srcCompat="@mipmap/ic_launcher" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/username_layout"
@@ -15,8 +15,7 @@
~ 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="@android:color/white"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
@@ -15,8 +15,7 @@
~ 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="@android:color/white"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 838 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 666 B

After

Width:  |  Height:  |  Size: 832 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

+3 -3
View File
@@ -106,6 +106,7 @@
<string name="content_layout_restore_tip">点击重试</string>
<string name="write_rationale">EhViewer 将申请设备存储空间访问权限以便下载画廊。您愿意授权吗?</string>
<string name="you_rejected_me">好痛</string>
<string name="analytics_explain">您同意让开发者收集您的设备信息与使用情况吗?这将对开发有很大帮助并将为您启用应用内更新。&lt;br&gt;&lt;br&gt;数据统计服务由 Microsoft AppCenter 提供,其隐私政策可在 &lt;a href=&quot;https://privacy.microsoft.com/zh-cn/privacystatement&quot;&gt;https://privacy.microsoft.com/zh-cn/privacystatement&lt;/a&gt; 查看。&lt;br&gt;&lt;br&gt;若您同意参与数据统计,您的设备信息与应用使用情况将会被记录,其包括但不限于:Android API 版本、设备型号、语言所在地、本应用版本号、会话时长、应用功能使用次数。开发者承诺以下信息并不会被记录:电话号码、电子邮件地址、IMEI。&lt;br&gt;&lt;br&gt;在您同意参与数据统计之前,您的信息不会被记录。&lt;br&gt;&lt;br&gt;被记录的数据会由 Microsoft AppCenter 进行分析,分析报表仅可被本应用开发者访问。</string>
<string name="get_it">知道了</string>
<string name="accept">接受</string>
<string name="reject">拒绝</string>
@@ -755,8 +756,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">对于您的设备,我们强烈建议您使用 1.8 版本。1.7 版本仅为 Android 11 及以下版本设计</string>
<string name="app_link_not_verified_title">升级 App 版本</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>
@@ -768,5 +769,4 @@
<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>
+8 -73
View File
@@ -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">ReadMe</string>
<string name="readme">讀我檔</string>
<string name="image_search_not_quick_search">無法將圖片搜尋加入快速搜尋</string>
<string name="duplicate_quick_search">已存在相同的快速搜尋,名稱為“%s”。</string>
<string name="duplicate_name">已經由重複的名稱了</string>
@@ -234,10 +234,7 @@
<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>
@@ -352,7 +349,6 @@
<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>
@@ -377,7 +373,6 @@
<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>
@@ -405,7 +400,6 @@
<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>
@@ -428,8 +422,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>
@@ -471,7 +465,6 @@
<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>
@@ -497,7 +490,6 @@
<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>
@@ -533,7 +525,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>
@@ -599,7 +591,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>
@@ -710,63 +702,6 @@
<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>
+4
View File
@@ -134,6 +134,10 @@
<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" />
@@ -0,0 +1,21 @@
<?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>
+2
View File
@@ -31,6 +31,8 @@
<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 -->
+3 -3
View File
@@ -148,6 +148,7 @@
<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.&lt;br&gt;&lt;br&gt;Data analysis service is provided by Microsoft AppCenter. Its privacy policy can be found at &lt;a href=&quot;https://privacy.microsoft.com/zh-cn/privacystatement&quot;&gt;https://privacy.microsoft.com/zh-cn/privacystatement&lt;/a&gt;.&lt;br&gt;&lt;br&gt;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.&lt;br&gt;&lt;br&gt;Your information will not be collected until you agree to join data analysis.&lt;br&gt;&lt;br&gt;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>
@@ -819,8 +820,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 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="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="open_by_default">Open by default</string>
<string name="show_library_version">Library Version</string>
@@ -832,5 +833,4 @@
<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>
-9
View File
@@ -82,15 +82,6 @@
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"
+1 -1
View File
@@ -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>
-55
View File
@@ -1,55 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

-10
View File
@@ -1,10 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 832 B

+1 -1
View File
@@ -1,5 +1,5 @@
plugins {
id 'com.android.application' version '7.2.2' apply false
id 'com.android.application' version '7.2.1' apply false
id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
}
-110
View File
@@ -1,110 +0,0 @@
<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
![screenshot-01](../../art/screenshot-01.png)
![screenshot-02](../../art/screenshot-02.png)
# 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/>.
-110
View File
@@ -1,110 +0,0 @@
<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 上正常工作
# 截图
![screenshot-01](../../art/screenshot-01.png)
![screenshot-02](../../art/screenshot-02.png)
# 感谢
本项目受到了诸多开源项目的帮助
- [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/>.
-110
View File
@@ -1,110 +0,0 @@
<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 上正常工作
# 截圖
![screenshot-01](../../art/screenshot-01.png)
![screenshot-02](../../art/screenshot-02.png)
# 感謝
本項目受到了諸多開源項目的幫助
- [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 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+1 -1
View File
@@ -9,7 +9,7 @@ android {
ndkVersion "25.0.8775105"
defaultConfig {
minSdkVersion 26
minSdkVersion 31
targetSdkVersion 33
consumerProguardFiles 'proguard-rules.pro'
@@ -28,8 +28,6 @@ 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 void extracttoOutputStream(int index, int fd);
public native long extracttoOutputStream(int index);
public native boolean needPassword();
+24 -77
View File
@@ -21,6 +21,7 @@ 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;
@@ -28,31 +29,9 @@ 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;
/**
* 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;
public static final int FORMAT_ANIMATED = 1;
private static final AtomicInteger sImageCount = new AtomicInteger();
private final int mFormat;
@@ -74,11 +53,24 @@ public final class Image {
* Decode image from {@code InputStream}
*/
@Nullable
public static Image decode(InputStream is, boolean partially) {
if (!(is instanceof BufferedInputStream)) {
is = new BufferedInputStream(is);
}
return nativeDecode(is, partially);
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);
}
/**
@@ -97,31 +89,16 @@ public final class Image {
return sImageCount.get();
}
/**
* Return all supported image formats, exclude {@link #FORMAT_PLAIN}
*/
public static int[] getSupportedImageFormats() {
return nativeGetSupportedImageFormats();
}
private static native Image nativeDecode(FileDescriptor 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 nativeDecodeFdInt(int fd, boolean partially);
private static native Image nativeDecode(InputStream is, boolean partially);
private static native Image nativeDecodeAddr(long addr, 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);
@@ -133,8 +110,6 @@ 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);
@@ -143,10 +118,6 @@ 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
*/
@@ -186,22 +157,6 @@ 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}
*/
@@ -239,14 +194,6 @@ 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,6 +28,7 @@ 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;
@@ -45,7 +46,6 @@ 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,9 +61,8 @@ public final class ImageBitmap implements Animatable, Runnable {
mFormat = image.getFormat();
mIsOpaque = image.isOpaque();
mByteCount = image.getByteCount();
mFrameCount = image.getFrameCount();
if (mFrameCount > 1) {
if (mFormat == 1) {
// For animated image, save image object
mImage = image;
} else {
@@ -73,19 +72,18 @@ public final class ImageBitmap implements Animatable, Runnable {
}
private ImageBitmap(@NonNull Bitmap bitmap) {
mFormat = Image.FORMAT_PLAIN;
mFormat = Image.FORMAT_NORMAL;
mBitmap = bitmap;
mIsOpaque = !bitmap.hasAlpha();
mByteCount = bitmap.getRowBytes() * bitmap.getHeight();
mFrameCount = 1;
}
/**
* Decode {@code InputStream}, then create image.
*/
@Nullable
public static ImageBitmap decode(@NonNull InputStream is) {
Image image = Image.decode(is, false);
public static ImageBitmap decode(@NonNull FileDescriptor fd) {
Image image = Image.decode(fd, false);
if (image != null) {
return create(image);
} else {
@@ -237,13 +235,6 @@ public final class ImageBitmap implements Animatable, Runnable {
return mImage != null;
}
/**
* Return image frame count
*/
public int getFrameCount() {
return mFrameCount;
}
/**
* Draw image to canvas
*/
+15 -12
View File
@@ -121,35 +121,37 @@ Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *_, jobject thiz, jlong add
return r;
}
JNIEXPORT void JNICALL
Java_com_hippo_UriArchiveAccessor_extracttoOutputStream(JNIEnv *_, jobject thiz, jint index,
jint fd) {
typedef struct Memarea {
void* buffer;
long size;
} Memarea;
JNIEXPORT jlong JNICALL
Java_com_hippo_UriArchiveAccessor_extracttoOutputStream(JNIEnv *_, jobject thiz, jint index) {
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_block(arc, &buff, &size, &offset);
if (ret == ARCHIVE_EOF)
break;
if (ret != ARCHIVE_OK) {
ret = archive_read_data(arc, memarea->buffer, memarea->size);
if (ret < 0) {
LOGE("%s%s", "Archive read failed:", archive_error_string(arc));
break;
}
write(fd, buff, size);
break;
}
break;
}
}
return (jlong) memarea;
}
JNIEXPORT void JNICALL
@@ -172,6 +174,7 @@ 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)))
+20 -5
View File
@@ -13,9 +13,24 @@
# limitations under the License.
cmake_minimum_required(VERSION 3.4.1)
project(image C)
add_subdirectory(image)
add_subdirectory(stream)
add_subdirectory(libjpeg-turbo)
add_subdirectory(libpng)
add_subdirectory(giflib)
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})
+186
View File
@@ -0,0 +1,186 @@
/*
* 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,12 +19,11 @@
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
@@ -1,27 +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.
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})
+192
View File
@@ -0,0 +1,192 @@
/*
* 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);
}
+69
View File
@@ -0,0 +1,69 @@
/*
* 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
@@ -1,60 +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.
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)
@@ -1,44 +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.
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})
-37
View File
@@ -1,37 +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_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
@@ -1,184 +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);
}
}
}
-532
View File
@@ -1,532 +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 "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;
}
}
-65
View File
@@ -1,65 +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 <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
-558
View File
@@ -1,558 +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 "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
@@ -1,84 +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_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
@@ -1,210 +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.
*/
//
// 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
@@ -1,64 +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.
*/
//
// 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
@@ -1,129 +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.
*/
//
// 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
@@ -1,56 +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.
*/
//
// 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
-642
View File
@@ -1,642 +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 "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
@@ -1,89 +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_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
@@ -1,185 +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 "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));
}
}
@@ -1,304 +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 <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;
}
+183
View File
@@ -0,0 +1,183 @@
/*
* 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));
}
}
Loaded 100 of 127 files, more files were not shown because too many files have changed in this diff. Show more