Refactor wp (#944)

* Extract couple filters to separate classes

* Move ImageEffects to separate file

* Refactor effects to use bitmap

* Reformat code, extract last batch of effects to classes

* Shitty intermediate refactor

* Still intermediate sh**, buildable btw

* More refactoring, still looks disgusting, but a bit better)

* Add fixme about order

* Remove unused stuff

* Remove unused code, prevent re-creating of drawable

* Refactor img effects to native

* Add native lib sources

* Implement Dim, Mosaic and Blur in native

* Add pre-built libs

* Get rid of filter abstraction

* Include native build

* Force rust toolchains

* Fix typos

* Comment not used smali

* Simplify wallpapers handling

* Fixes after main merge

* Minor fixes, add prebuilt lib, disable auto re-builds for native

* Fix missed copyscpec for native builds

* Fix entry for dim effect

* Fix resizing to respect current image dimensions

* Wtf? who stole NDK from actions

* Learning GH actions

I don't want to read the docs

* Revert "Learning GH actions"

This reverts commit f6f9a97d10.

* Revert "Wtf? who stole NDK from actions"

This reverts commit 1d0f1b8879.

* Try out latest LTS just why not

---------

Co-authored-by: saoq <wfmfe@ya.ru>
This commit is contained in:
Saoqq
2023-02-21 09:20:35 +03:00
committed by GitHub
co-authored by saoq
parent 0d8c52548f
commit a45e6a7f2a
16 changed files with 1013 additions and 718 deletions
@@ -0,0 +1,245 @@
package ru.vtosters.lite.ui.wallpapers;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.vtosters.lite.R;
import ru.vtosters.lite.utils.AndroidUtils;
import java.nio.ByteBuffer;
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
import static ru.vtosters.lite.utils.AndroidUtils.getPreferences;
enum ImageEffects {
Blur(true,
AndroidUtils.getArray(R.array.filter_types),
new String[]{"disabled", "low", "med", "high"},
getGlobalContext().getString(R.string.filter_blur)
) {
@Override
public void applyEffect(ByteBuffer bitmap, int height, int width) {
int radius;
switch (getPreferences().getString(ImageEffects.Blur.toString(), "disabled")) {
case "low":
radius = 8;
break;
case "med":
radius = 14;
break;
case "high":
radius = 20;
break;
default:
return;
}
NativeEffects.gaussian(bitmap, height, width, radius);
}
@Override
public String getSummary() {
switch (getPreferences().getString(ImageEffects.Blur.toString(), "disabled")) {
case "low":
return AndroidUtils.getString(R.string.wallpapers_low);
case "med":
return AndroidUtils.getString(R.string.wallpapers_med);
case "high":
return AndroidUtils.getString(R.string.wallpapers_high);
default:
return AndroidUtils.getString(R.string.wallpapers_disabled);
}
}
},
Dim(true,
AndroidUtils.getArray(R.array.filter_dim_types),
new String[]{"disabled", "dim_black", "dim_white"},
getGlobalContext().getString(R.string.filter_dim)) {
@Override
public void applyEffect(ByteBuffer bitmap, int height, int width) {
var delta = 0;
switch (getPreferences().getString(ImageEffects.Dim.toString(), "disabled")) {
case "dim_black":
delta = -50; break;
case "dim_white":
delta = 50; break;
default:
return;
}
NativeEffects.dim(bitmap, height, width, delta);
}
@Override
public String getSummary() {
switch (getPreferences().getString(ImageEffects.Dim.toString(), "disabled")) {
case "dim_black":
return AndroidUtils.getString(R.string.wallpapers_dim_black);
case "dim_white":
return AndroidUtils.getString(R.string.wallpapers_dim_white);
default:
return AndroidUtils.getString(R.string.wallpapers_disabled);
}
}
},
Mosaic(true,
AndroidUtils.getArray(R.array.filter_types),
new String[]{"disabled", "low", "med", "high"},
getGlobalContext().getString(R.string.filter_mosaic)) {
@Override
public void applyEffect(ByteBuffer bitmap, int height, int width) {
var scale = 0;
switch (getPreferences().getString(ImageEffects.Mosaic.toString(), "disabled")) {
case "high":
scale = 50; break;
case "med":
scale = 25; break;
case "low":
scale = 10; break;
default:
return;
}
NativeEffects.mosaic(bitmap, height, width, scale);
}
@Override
public String getSummary() {
switch (getPreferences().getString(ImageEffects.Mosaic.toString(), "disabled")) {
case "low":
return AndroidUtils.getString(R.string.wallpapers_low);
case "med":
return AndroidUtils.getString(R.string.wallpapers_med);
case "high":
return AndroidUtils.getString(R.string.wallpapers_high);
default:
return AndroidUtils.getString(R.string.wallpapers_disabled);
}
}
},
Monochrome(false, null, null,
getGlobalContext().getString(R.string.filter_monochrome)) {
@Override
public void applyEffect(ByteBuffer bitmap, int height, int width) {
if (isApplied()) {
NativeEffects.monochrome(bitmap);
}
}
},
Invert(false, null, null,
getGlobalContext().getString(R.string.filter_invert_colors)) {
@Override
public void applyEffect(ByteBuffer bitmap, int height, int width) {
if (isApplied()) {
NativeEffects.invert(bitmap);
}
}
},
Sepia(false, null, null,
getGlobalContext().getString(R.string.filter_sepia)) {
@Override
public void applyEffect(ByteBuffer bitmap, int height, int width) {
if (isApplied()) {
NativeEffects.sepia(bitmap);
}
}
},
Emboss(false, null, null,
getGlobalContext().getString(R.string.filter_emboss)) {
@Override
public String getSummary() {
return getGlobalContext().getString(R.string.filter_maybe_lag);
}
@Override
public void applyEffect(ByteBuffer bitmap, int height, int width) {
if (isApplied()) {
NativeEffects.emboss(bitmap, height, width);
}
}
},
Engrave(false, null, null,
getGlobalContext().getString(R.string.filter_engrave)) {
@Override
public String getSummary() {
return getGlobalContext().getString(R.string.filter_maybe_lag);
}
@Override
public void applyEffect(ByteBuffer bitmap, int height, int width) {
if (isApplied()) {
NativeEffects.engrave(bitmap, height, width);
}
}
},
Flea(false, null, null,
getGlobalContext().getString(R.string.filter_flea)) {
@Override
public void applyEffect(ByteBuffer bitmap, int height, int width) {
if (isApplied()) {
NativeEffects.flea(bitmap);
}
}
},
Snow(false, null, null,
getGlobalContext().getString(R.string.filter_snow)) {
@Override
public void applyEffect(ByteBuffer bitmap, int height, int width) {
if (isApplied()){
NativeEffects.snow(bitmap);
}
}
};
private final boolean free;
private final String title;
@Nullable
private final String[] entryValues;
@Nullable
private final String[] entries;
ImageEffects(boolean free, @Nullable String[] entries, @Nullable String[] entryValues, String title) {
this.free = free;
this.entries = entries;
this.entryValues = entryValues;
this.title = title;
}
public String getTitle() {
return title;
}
public Boolean isSwitch() {
return entryValues == null || entryValues.length == 0;
}
public Boolean isList() {
return entryValues != null && entryValues.length > 0;
}
public boolean isFree() {
return free;
}
@Nullable
public String[] getEntries() {
return entries;
}
@Nullable
public String[] getEntryValues() {
return entryValues;
}
public String getSummary() {
return "";
}
public abstract void applyEffect(ByteBuffer bitmap, int height, int width);
protected boolean isApplied() {
return getPreferences().getBoolean(this.toString(), false);
}
@NonNull
@Override
public String toString() {
return "msg_" + super.toString();
}
}
@@ -1,351 +1,37 @@
package ru.vtosters.lite.ui.wallpapers;
import static ru.vtosters.lite.utils.AndroidUtils.getPreferences;
import static ru.vtosters.lite.utils.AndroidUtils.getResources;
import static ru.vtosters.lite.utils.Preferences.hasVerification;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.LightingColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.Log;
import com.vk.medianative.MediaNative;
import java.util.Random;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class ImageFilters {
public static final int COLOR_MAX = 0xFF;
public static Drawable getFilteredDrawable(Drawable orig) {
if (orig == null) return null;
if (!(orig instanceof BitmapDrawable)) return orig;
if (!getPreferences().getString("msg_blur_radius", "disabled").equals("disabled")) {
orig = getBlurredWallpaper(orig, getRadius());
Bitmap bitmap = ((BitmapDrawable) orig).getBitmap();
if (bitmap.getConfig() != Bitmap.Config.ARGB_8888) {
bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
}
if (!getPreferences().getString("msg_dim", "off").equals("off")) {
orig = getDimmed(orig);
var directBuff = ByteBuffer.allocateDirect(bitmap.getByteCount());
directBuff.order(ByteOrder.nativeOrder());
bitmap.copyPixelsToBuffer(directBuff);
// FIXME: preserve order of filters
// e.g. user turned invert first then mosaic then monochrome
// invocation here happens regardless of that order
for (var type : ImageEffects.values()) {
type.applyEffect(directBuff, bitmap.getHeight(), bitmap.getWidth());
}
if (!getPreferences().getString("msg_mosaic", "disabled").equals("disabled")) {
orig = getMosaic(orig);
}
if (!hasVerification()) {
return orig;
}
if (getPreferences().getBoolean("msg_monochrome", false)) {
orig = getMonochrome(orig);
}
Bitmap bm;
if (orig != null) {
bm = ((BitmapDrawable) orig).getBitmap();
} else {
return null;
}
Bitmap instance = bm.copy(Bitmap.Config.ARGB_8888, true);
if (getPreferences().getBoolean("msg_invert", false)) {
instance = applyInvertEffect(instance);
}
if (getPreferences().getBoolean("msg_sepia", false)) {
instance = applySepiaEffect(instance);
}
if (getPreferences().getBoolean("msg_emboss", false)) {
instance = applyEmbossEffect(instance);
}
if (getPreferences().getBoolean("msg_engrave", false)) {
instance = applyEngraveEffect(instance);
}
if (getPreferences().getBoolean("msg_flea", false)) {
instance = applyFleaEffect(instance);
}
if (getPreferences().getBoolean("msg_snow", false)) {
instance = applySnowEffect(instance);
}
return new BitmapDrawable(getResources(), instance);
}
private static int getRadius() {
String radius = getPreferences().getString("msg_blur_radius", "disabled");
return switch (radius) {
case "low" -> 8;
case "med" -> 14;
default -> 20;
};
}
public static Drawable getBlurredWallpaper(Drawable orig, int radius) {
try {
if (orig == null) return null;
if (!(orig instanceof BitmapDrawable)) return orig; // we need only BitmapDrawable
Bitmap bm = ((BitmapDrawable) orig).getBitmap();
Bitmap instance = bm.copy(Bitmap.Config.ARGB_8888, true);
MediaNative.blurBitmap(instance, radius);
return new BitmapDrawable(getResources(), instance);
} catch (Exception e) {
e.printStackTrace();
return orig;
}
}
public static Drawable getMosaic(Drawable orig) {
int scale = 100;
if (orig == null) return null;
if (!(orig instanceof BitmapDrawable)) return orig;
Bitmap bm = ((BitmapDrawable) orig).getBitmap();
Bitmap instance = bm.copy(Bitmap.Config.ARGB_8888, true);
String radius = getPreferences().getString("msg_mosaic", "disabled");
switch (radius) {
case "high" -> scale = 25;
case "med" -> scale = 50;
case "low" -> scale = 75;
}
Bitmap temp = Bitmap.createScaledBitmap(instance, scale, scale, false);
Bitmap mosaicBitmap = Bitmap.createScaledBitmap(temp, instance.getWidth(), instance.getHeight(), false);
return new BitmapDrawable(getResources(), mosaicBitmap);
}
public static Drawable getDimmed(Drawable orig) {
if (orig == null) return null;
if (!(orig instanceof BitmapDrawable)) return orig;
Bitmap bm = ((BitmapDrawable) orig).getBitmap();
Bitmap instance = bm.copy(Bitmap.Config.ARGB_8888, true);
String radius = getPreferences().getString("msg_dim", "disabled");
switch (radius) {
case "dim_black":
darken(instance);
case "dim_white":
lighten(instance);
}
return new BitmapDrawable(getResources(), instance);
}
public static Drawable getMonochrome(Drawable orig) {
if (orig == null) return null;
if (!(orig instanceof BitmapDrawable)) return orig;
Bitmap bm = ((BitmapDrawable) orig).getBitmap();
Bitmap instance = bm.copy(Bitmap.Config.ARGB_8888, true);
Bitmap bwBitmap = Bitmap.createBitmap(instance.getWidth(), instance.getHeight(), Bitmap.Config.RGB_565);
float[] hsv = new float[3];
for (int col = 0; col < instance.getWidth(); col++) {
for (int row = 0; row < instance.getHeight(); row++) {
Color.colorToHSV(instance.getPixel(col, row), hsv);
if (hsv[2] > 0.5f) {
bwBitmap.setPixel(col, row, 0xffffffff);
} else {
bwBitmap.setPixel(col, row, 0xff000000);
}
}
}
return new BitmapDrawable(getResources(), bwBitmap);
}
private static void darken(Bitmap bm) {
try {
Canvas canvas = new Canvas(bm);
Paint p = new Paint(Color.RED);
ColorFilter filter = new LightingColorFilter(0xFF7F7F7F, 0x00000000); // darken
p.setColorFilter(filter);
canvas.drawBitmap(bm, new Matrix(), p);
} catch (Exception e) {
Log.d("WallpaperD", e.getMessage());
}
}
private static void lighten(Bitmap bm) {
try {
Canvas canvas = new Canvas(bm);
Paint p = new Paint(Color.RED);
ColorFilter filter = new LightingColorFilter(0xFFFFFFFF, 0x00222222); // lighten
p.setColorFilter(filter);
canvas.drawBitmap(bm, new Matrix(), p);
} catch (Exception e) {
Log.d("WallpaperL", e.getMessage());
}
}
public static Bitmap applyInvertEffect(Bitmap src) {
// create new bitmap with the same settings as source bitmap
Bitmap bmOut = Bitmap.createBitmap(src.getWidth(), src.getHeight(), src.getConfig());
// color info
int A, R, G, B;
int pixelColor;
// image size
int height = src.getHeight();
int width = src.getWidth();
// scan through every pixel
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
// get one pixel
pixelColor = src.getPixel(x, y);
// saving alpha channel
A = Color.alpha(pixelColor);
// inverting byte for each R/G/B channel
R = 255 - Color.red(pixelColor);
G = 255 - Color.green(pixelColor);
B = 255 - Color.blue(pixelColor);
// set newly-inverted pixel to output image
bmOut.setPixel(x, y, Color.argb(A, R, G, B));
}
}
// return final bitmap
return bmOut;
}
public static Bitmap applySepiaEffect(Bitmap src) {
// image size
int width = src.getWidth();
int height = src.getHeight();
// create output bitmap
Bitmap bmOut = Bitmap.createBitmap(width, height, src.getConfig());
// constant grayscale
final double GS_RED = 0.3;
final double GS_GREEN = 0.59;
final double GS_BLUE = 0.11;
// color information
int A, R, G, B;
int pixel;
// scan through all pixels
for (int x = 0; x < width; ++x) {
for (int y = 0; y < height; ++y) {
// get pixel color
pixel = src.getPixel(x, y);
// get color on each channel
A = Color.alpha(pixel);
R = Color.red(pixel);
G = Color.green(pixel);
B = Color.blue(pixel);
// apply grayscale sample
B = (int) (GS_RED * R + GS_GREEN * G + GS_BLUE * B);
// apply intensity level for sepia tone
R = G = B = (int) (B * 0.9);
// set new pixel color to output bitmap
bmOut.setPixel(x, y, Color.argb(A, R, G, B));
}
}
// return final image
return bmOut;
}
public static Bitmap applyEmbossEffect(Bitmap src) {
double[][] EmbossConfig = new double[][]{
{-1, 0, -1},
{0, 4, 0},
{-1, 0, -1}
};
ConvolutionMatrix convMatrix = new ConvolutionMatrix(3);
convMatrix.applyConfig(EmbossConfig);
convMatrix.Factor = 1;
convMatrix.Offset = 127;
return ConvolutionMatrix.computeConvolution3x3(src, convMatrix);
}
public static Bitmap applyEngraveEffect(Bitmap src) {
ConvolutionMatrix convMatrix = new ConvolutionMatrix(3);
convMatrix.setAll(0);
convMatrix.Matrix[0][0] = -2;
convMatrix.Matrix[1][1] = 2;
convMatrix.Factor = 1;
convMatrix.Offset = 95;
return ConvolutionMatrix.computeConvolution3x3(src, convMatrix);
}
public static Bitmap applyFleaEffect(Bitmap source) {
// get image size
int width = source.getWidth();
int height = source.getHeight();
int[] pixels = new int[width * height];
// get pixel array from source
source.getPixels(pixels, 0, width, 0, 0, width, height);
// a random object
Random random = new Random();
int index;
// iteration through pixels
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
// get current index in 2D-matrix
index = y * width + x;
// get random color
int randColor = Color.rgb(random.nextInt(COLOR_MAX),
random.nextInt(COLOR_MAX), random.nextInt(COLOR_MAX));
// OR
pixels[index] |= randColor;
}
}
// output bitmap
Bitmap bmOut = Bitmap.createBitmap(width, height, source.getConfig());
bmOut.setPixels(pixels, 0, width, 0, 0, width, height);
return bmOut;
}
public static Bitmap applySnowEffect(Bitmap source) {
// get image size
int width = source.getWidth();
int height = source.getHeight();
int[] pixels = new int[width * height];
// get pixel array from source
source.getPixels(pixels, 0, width, 0, 0, width, height);
// random object
Random random = new Random();
int R, G, B, index, thresHold;
// iteration through pixels
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
// get current index in 2D-matrix
index = y * width + x;
// get color
R = Color.red(pixels[index]);
G = Color.green(pixels[index]);
B = Color.blue(pixels[index]);
// generate threshold
thresHold = random.nextInt(COLOR_MAX);
if (R > thresHold && G > thresHold && B > thresHold) {
pixels[index] = Color.rgb(COLOR_MAX, COLOR_MAX, COLOR_MAX);
}
}
}
// output bitmap
Bitmap bmOut = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
bmOut.setPixels(pixels, 0, width, 0, 0, width, height);
return bmOut;
directBuff.flip();
bitmap.copyPixelsFromBuffer(directBuff);
return new BitmapDrawable(getResources(), bitmap);
}
}
@@ -0,0 +1,22 @@
package ru.vtosters.lite.ui.wallpapers;
import java.nio.ByteBuffer;
public class NativeEffects {
static {
System.loadLibrary("native_effects");
}
public static native String monochrome(ByteBuffer input);
public static native String invert(ByteBuffer input);
public static native String sepia(ByteBuffer input);
public static native String emboss(ByteBuffer input, int height, int width);
public static native String engrave(ByteBuffer input, int height, int width);
public static native String flea(ByteBuffer input);
public static native String snow(ByteBuffer input);
// FIXME: slow when comparing with MediaNative.blurBitmap
public static native String gaussian(ByteBuffer input, int height, int width, float radius);
public static native String dim(ByteBuffer input, int height, int width, int delta);
public static native String mosaic(ByteBuffer input, int height, int width, int scale);
}
@@ -7,17 +7,16 @@ import android.net.Uri;
import android.os.Bundle;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.preference.ListPreference;
import com.vtosters.lite.R;
import com.vtosters.lite.general.fragments.MaterialPreferenceToolbarFragment;
import com.vtosters.lite.im.ImEngineProvider;
import ru.vtosters.lite.ui.PreferenceFragmentUtils;
import ru.vtosters.lite.utils.AndroidUtils;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import static ru.vtosters.lite.ui.wallpapers.WallpapersHooks.*;
import static ru.vtosters.lite.utils.AndroidUtils.edit;
@@ -29,7 +28,6 @@ public class WallpaperMenuFragment extends MaterialPreferenceToolbarFragment {
private WallpaperPreferences mWPPreviewPref;
public static void copyStream(InputStream copyFrom, OutputStream copyTo) throws IOException {
byte[] buffer = new byte[1024];
int length;
while ((length = copyFrom.read(buffer)) > 0) {
@@ -47,146 +45,117 @@ public class WallpaperMenuFragment extends MaterialPreferenceToolbarFragment {
addPreferencesFromResource(R.xml.empty);
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), requireContext().getString(R.string.vkim_settings_appearance_chat_preview_title));
PreferenceFragmentUtils.addPreferenceCategory(
getPreferenceScreen(),
requireContext().getString(R.string.vkim_settings_appearance_chat_preview_title)
);
mWPPreviewPref = new WallpaperPreferences(getContext());
mWPPreviewPref.setIcon(null);
mWPPreviewPref.setIconSpaceReserved(false);
getPreferenceScreen().addPreference(mWPPreviewPref);
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "wp_set", requireContext().getString(R.string.wallpaper_select), requireContext().getString(R.string.wallpaper_from_gallery), R.drawable.ic_picture_outline_28, preference -> {
startActivityForResult(new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI), 1488);
removeStickCache();
return true;
});
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"wp_set",
requireContext().getString(R.string.wallpaper_select),
requireContext().getString(R.string.wallpaper_from_gallery),
R.drawable.ic_picture_outline_28,
preference -> {
startActivityForResult(
new Intent(
Intent.ACTION_PICK,
android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI
),
1488
);
removeStickCache();
return true;
});
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "wp_clear", requireContext().getString(R.string.wallpaper_remove), "", R.drawable.ic_delete_outline_28, preference -> {
removeWallpaper();
requestUpdateWallpaper();
mWPPreviewPref.redraw();
removeStickCache();
return true;
});
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"wp_clear",
requireContext().getString(R.string.wallpaper_remove),
"",
R.drawable.ic_delete_outline_28,
preference -> {
removeWallpaper();
requestUpdateWallpaper();
mWPPreviewPref.redraw();
removeStickCache();
return true;
});
PreferenceFragmentUtils.addPreferenceCategory(getPreferenceScreen(), requireContext().getString(R.string.vtlfilters));
if (!hasVerification() && !getBoolValue("dialogrecomm", false)) {
PreferenceFragmentUtils.addPreference(getPreferenceScreen(), "", requireContext().getString(R.string.filters_warning), requireContext().getString(R.string.icons_warning_info), 0, preference -> {
requireContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse("https://vtosters.app/donate/")));
return false;
});
PreferenceFragmentUtils.addPreference(
getPreferenceScreen(),
"",
requireContext().getString(R.string.filters_warning),
requireContext().getString(R.string.icons_warning_info),
0,
preference -> {
requireContext().startActivity(
new Intent(Intent.ACTION_VIEW)
.setData(Uri.parse("https://vtosters.app/donate/"))
);
return false;
});
}
PreferenceFragmentUtils.addListPreference(getPreferenceScreen(), "msg_blur_radius", "disabled", requireContext().getString(R.string.filter_blur), AndroidUtils.getArray(R.array.filter_types), new String[]{
"disabled", "low", "med", "high"
});
Arrays.stream(ImageEffects.values())
.filter(imageEffects -> imageEffects.isFree() || hasVerification())
.forEach(it -> {
if (it.isList()) {
var pref = PreferenceFragmentUtils.addListPreference(
getPreferenceScreen(),
it.toString(),
"disabled",
it.getTitle(),
it.getEntries(),
it.getEntryValues()
);
pref.setSummary(it.getSummary());
pref.setOnPreferenceChangeListener((preference, o) -> {
edit().putString(it.toString(), (String) o).apply();
preference.setSummary(it.getSummary());
requestUpdateWallpaper();
mWPPreviewPref.redraw();
return true;
});
} else if (it.isSwitch()) {
PreferenceFragmentUtils.addMaterialSwitchPreference(
getPreferenceScreen(),
it.toString(),
it.getTitle(),
it.getSummary(),
0,
false,
(preference, o) -> {
edit().putBoolean(it.toString(), (Boolean) o).apply();
requestUpdateWallpaper();
mWPPreviewPref.redraw();
return true;
});
}
});
PreferenceFragmentUtils.addListPreference(getPreferenceScreen(), "msg_dim", "off", requireContext().getString(R.string.filter_dim), AndroidUtils.getArray(R.array.filter_dim_types), new String[]{
"off", "dim_black", "dim_white"
});
PreferenceFragmentUtils.addListPreference(getPreferenceScreen(), "msg_mosaic", "disabled", requireContext().getString(R.string.filter_mosaic), AndroidUtils.getArray(R.array.filter_types), new String[]{
"disabled", "low", "med", "high"
});
if (hasVerification()) {
PreferenceFragmentUtils.addMaterialSwitchPreference(getPreferenceScreen(), "msg_monochrome", requireContext().getString(R.string.filter_monochrome), "", 0, false, (preference, o) -> {
boolean value = (boolean) o;
edit().putBoolean("msg_monochrome", value).commit();
requestUpdateWallpaper();
mWPPreviewPref.redraw();
return true;
});
PreferenceFragmentUtils.addMaterialSwitchPreference(getPreferenceScreen(), "msg_invert", requireContext().getString(R.string.filter_invert_colors), "", 0, false, (preference, o) -> {
boolean value = (boolean) o;
edit().putBoolean("msg_invert", value).commit();
requestUpdateWallpaper();
mWPPreviewPref.redraw();
return true;
});
PreferenceFragmentUtils.addMaterialSwitchPreference(getPreferenceScreen(), "msg_sepia", requireContext().getString(R.string.filter_sepia), "", 0, false, (preference, o) -> {
boolean value = (boolean) o;
edit().putBoolean("msg_sepia", value).commit();
requestUpdateWallpaper();
mWPPreviewPref.redraw();
return true;
});
PreferenceFragmentUtils.addMaterialSwitchPreference(getPreferenceScreen(), "msg_emboss", requireContext().getString(R.string.filter_emboss), requireContext().getString(R.string.filter_maybe_lag), 0, false, (preference, o) -> {
boolean value = (boolean) o;
edit().putBoolean("msg_emboss", value).commit();
requestUpdateWallpaper();
mWPPreviewPref.redraw();
return true;
});
PreferenceFragmentUtils.addMaterialSwitchPreference(getPreferenceScreen(), "msg_engrave", requireContext().getString(R.string.filter_engrave), requireContext().getString(R.string.filter_maybe_lag), 0, false, (preference, o) -> {
boolean value = (boolean) o;
edit().putBoolean("msg_engrave", value).commit();
requestUpdateWallpaper();
mWPPreviewPref.redraw();
return true;
});
PreferenceFragmentUtils.addMaterialSwitchPreference(getPreferenceScreen(), "msg_flea", requireContext().getString(R.string.filter_flea), "", 0, false, (preference, o) -> {
boolean value = (boolean) o;
edit().putBoolean("msg_flea", value).commit();
requestUpdateWallpaper();
mWPPreviewPref.redraw();
return true;
});
PreferenceFragmentUtils.addMaterialSwitchPreference(getPreferenceScreen(), "msg_snow", requireContext().getString(R.string.filter_flea), "", 0, false, (preference, o) -> {
boolean value = (boolean) o;
edit().putBoolean("msg_snow", value).commit();
requestUpdateWallpaper();
mWPPreviewPref.redraw();
return true;
});
}
PreferenceFragmentUtils.addMaterialSwitchPreference(getPreferenceScreen(), "compresswp", requireContext().getString(R.string.compress_wallpaper_title), requireContext().getString(R.string.compress_wallpaper_summ), 0, true, (preference, o) -> {
boolean value = (boolean) o;
edit().putBoolean("compresswp", value).commit();
requestUpdateWallpaper();
mWPPreviewPref.redraw();
return true;
});
ListPreference blur = (ListPreference) findPreference("msg_blur_radius");
ListPreference dim = (ListPreference) findPreference("msg_dim");
ListPreference mosaic = (ListPreference) findPreference("msg_mosaic");
blur.setSummary(getRadiusSummary());
dim.setSummary(getDimmingSummary());
mosaic.setSummary(getMosaicSummary());
blur.setOnPreferenceChangeListener((preference, o) -> {
edit().putString("msg_blur_radius", (String) o).apply();
preference.setSummary(getRadiusSummary());
requestUpdateWallpaper();
mWPPreviewPref.redraw();
return true;
});
dim.setOnPreferenceChangeListener((preference, o) -> {
edit().putString("msg_dim", (String) o).apply();
preference.setSummary(getDimmingSummary());
requestUpdateWallpaper();
mWPPreviewPref.redraw();
return true;
});
mosaic.setOnPreferenceChangeListener((preference, o) -> {
edit().putString("msg_mosaic", (String) o).apply();
preference.setSummary(getMosaicSummary());
requestUpdateWallpaper();
mWPPreviewPref.redraw();
return true;
});
PreferenceFragmentUtils.addMaterialSwitchPreference(
getPreferenceScreen(),
"compresswp",
requireContext().getString(R.string.compress_wallpaper_title),
requireContext().getString(R.string.compress_wallpaper_summ),
0,
true,
(preference, o) -> {
boolean value = (boolean) o;
edit().putBoolean("compresswp", value).commit();
requestUpdateWallpaper();
mWPPreviewPref.redraw();
return true;
});
}
@Override
@@ -1,7 +1,6 @@
package ru.vtosters.lite.ui.wallpapers;
import static ru.vtosters.lite.ui.wallpapers.WallpapersHooks.getFilteredFile;
import static ru.vtosters.lite.ui.wallpapers.WallpapersHooks.getWallpaperFile;
import static ru.vtosters.lite.ui.wallpapers.WallpapersHooks.*;
import static ru.vtosters.lite.utils.AndroidUtils.dp2px;
import android.annotation.SuppressLint;
@@ -59,12 +58,8 @@ public class WallpaperPreferences extends Preference {
}
private void requestBg() {
final Drawable drawable = getFilteredFile();
if (drawable != null && getWallpaperFile() != null)
mChatBackground.setImageDrawable(drawable);
else
mChatBackground.setImageDrawable(null);
final Drawable drawable = getWallpaper();
mChatBackground.setImageDrawable(drawable);
}
@SuppressLint("NotifyDataSetChanged")
@@ -1,78 +1,97 @@
package ru.vtosters.lite.ui.wallpapers;
import static com.vk.im.engine.h.im_bg_chat;
import static ru.vtosters.lite.ui.wallpapers.ImageFilters.getFilteredDrawable;
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
import static ru.vtosters.lite.utils.AndroidUtils.getPreferences;
import static ru.vtosters.lite.utils.AndroidUtils.sendToast;
import static ru.vtosters.lite.utils.Preferences.getBoolValue;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.widget.ImageView;
import com.vtosters.lite.R;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import ru.vtosters.lite.utils.AndroidUtils;
import ru.vtosters.lite.utils.ThemesUtils;
import vigo.sdk.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.util.function.Supplier;
import static com.vk.im.engine.h.im_bg_chat;
import static ru.vtosters.lite.ui.wallpapers.ImageFilters.getFilteredDrawable;
import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext;
import static ru.vtosters.lite.utils.AndroidUtils.sendToast;
import static ru.vtosters.lite.utils.Preferences.getBoolValue;
public class WallpapersHooks {
private static final boolean compress = getBoolValue("compresswp", true);
private static File mWallpaperFile;
// TODO: force compress by default not asking user about that?
private static final Supplier<Boolean> compress = () -> getBoolValue("compresswp", true);
private static final File originalWp = new File(getGlobalContext().getFilesDir(), "wallpaper.jpeg");
private static final File compressedWp = new File(getGlobalContext().getFilesDir(), "compressedwp.jpeg");
// TODO: cache to file?
private static Drawable mWallpaper;
private static boolean mUpdateWallpaperRequested = true;
private static boolean mUpdateWallpaperFileRequested = true;
public static File getWallpaperFile() {
if (mWallpaperFile == null) {
mWallpaperFile = new File(getGlobalContext().getFilesDir(), "wallpaper.webp");
// TODO: respect blur, when there is blur/mosaic effect applied these two can be even less, just HD (1280x720) for e.g.
private static final int MAX_WP_WIDTH = 1080;
private static final int MAX_WP_HEIGHT = 1920;
public static void setBg(View view) {
if (hasWallpapers()) {
((ImageView) view).setImageDrawable(getWallpaper()); // set picture to background
} else {
view.setBackgroundColor(ThemesUtils.getColorFromAttr(im_bg_chat)); // set default bg color
}
return mWallpaperFile;
}
public static Drawable getFilteredFile() {
File file = null;
File wp = mWallpaperFile;
String temp = getGlobalContext().getFilesDir() + File.separator + "filteredwp.webp";
if (wp != null && mUpdateWallpaperFileRequested) {
try {
file = new File(temp);
file.createNewFile();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
((BitmapDrawable) getFilteredDrawable(Drawable.createFromPath(wp.getAbsolutePath()))).getBitmap().compress(Bitmap.CompressFormat.WEBP, (compress ? 80 : 95), bos);
byte[] bitmapdata = bos.toByteArray();
FileOutputStream fos = new FileOutputStream(file);
fos.write(bitmapdata);
fos.flush();
fos.close();
} catch (Exception e) {
Log.d("Wallpapers", e.getMessage());
}
mUpdateWallpaperFileRequested = false;
}
try {
return Drawable.createFromPath(temp);
} catch (Exception e) {
if (file != null) {
return Drawable.createFromPath(file.getAbsolutePath());
} else {
public static Drawable getWallpaper() {
if (mWallpaper == null || mUpdateWallpaperRequested) {
if (!originalWp.exists()) {
return null;
}
Drawable drawable;
if (compress.get()) {
if (!compressedWp.exists() && !prepareCompressed()) {
return null;
}
drawable = Drawable.createFromPath(compressedWp.getAbsolutePath());
} else {
drawable = Drawable.createFromPath(originalWp.getAbsolutePath());
}
if (drawable == null) {
return null;
}
mWallpaper = getFilteredDrawable(drawable);
mUpdateWallpaperRequested = false;
}
return mWallpaper;
}
private static boolean prepareCompressed() {
// TODO remove this check? with "compress" enabled it should be fine
if (!eligibleWallpaperFile(originalWp)) {
return false;
}
try (var f = new FileOutputStream(compressedWp)) {
var bitmap = BitmapFactory.decodeFile(originalWp.getAbsolutePath());
if (bitmap.getHeight() > MAX_WP_HEIGHT || bitmap.getWidth() > MAX_WP_WIDTH) {
bitmap = resize(bitmap, MAX_WP_WIDTH, MAX_WP_HEIGHT);
}
var drawable = new BitmapDrawable(null, bitmap);
// TODO: remove duplication without changes when provided image already fits required params
drawable.getBitmap().compress(Bitmap.CompressFormat.WEBP, 90, f);
} catch (Exception e) {
Log.d("Wallpapers", e.getMessage());
return false;
}
return true;
}
public static File getWallpaperFile() {
return originalWp;
}
private static Boolean eligibleWallpaperFile(File file) {
@@ -85,94 +104,40 @@ public class WallpapersHooks {
}
}
public static void setBg(View view) {
if (hasWallpapers()) {
((ImageView) view).setImageDrawable(getFilteredFile()); // set picture to background
} else {
view.setBackgroundColor(ThemesUtils.getColorFromAttr(im_bg_chat)); // set default bg color
}
}
public static String getWallpaperUrl() {
var file = getWallpaperFile();
if (file.exists() && eligibleWallpaperFile(file)) {
return file.getAbsolutePath();
}
return "default";
}
public static boolean hasWallpapers() {
return getWallpaper() != null;
}
public static Drawable getWallpaper() {
if (mWallpaper == null || mUpdateWallpaperRequested) {
String url = getWallpaperUrl();
if (url.equals("default") || url.isEmpty()) {
return null;
}
Drawable drawable = Drawable.createFromPath(getWallpaperUrl());
if (drawable == null) {
return null;
}
mWallpaper = drawable;
mWallpaper = getFilteredDrawable(mWallpaper);
mUpdateWallpaperRequested = false;
}
return mWallpaper;
}
public static void requestUpdateWallpaper() {
mUpdateWallpaperRequested = true;
mUpdateWallpaperFileRequested = true;
}
public static String getRadiusSummary() {
String radius = getPreferences().getString("msg_blur_radius", "disabled");
return switch (radius) {
case "low" -> AndroidUtils.getString(R.string.wallpapers_low);
case "med" -> AndroidUtils.getString(R.string.wallpapers_med);
case "high" -> AndroidUtils.getString(R.string.wallpapers_high);
default -> AndroidUtils.getString(R.string.wallpapers_disabled);
};
}
public static String getDimmingSummary() {
String radius = getPreferences().getString("msg_dim", "disabled");
return switch (radius) {
case "dim_black" -> AndroidUtils.getString(R.string.wallpapers_dim_black);
case "dim_white" -> AndroidUtils.getString(R.string.wallpapers_dim_white);
default -> AndroidUtils.getString(R.string.wallpapers_disabled);
};
}
public static String getMosaicSummary() {
String radius = getPreferences().getString("msg_mosaic", "disabled");
return switch (radius) {
case "low" -> AndroidUtils.getString(R.string.wallpapers_low);
case "med" -> AndroidUtils.getString(R.string.wallpapers_med);
case "high" -> AndroidUtils.getString(R.string.wallpapers_high);
default -> AndroidUtils.getString(R.string.wallpapers_disabled);
};
}
public static void removeWallpaper() {
try {
new File(getGlobalContext().getFilesDir(), "wallpaper.webp").delete();
new File(getGlobalContext().getFilesDir(), "filteredwp.webp").delete();
mWallpaperFile = null;
mWallpaper = null;
originalWp.delete();
compressedWp.delete();
} catch (Exception e) {
Log.d("Wallpapers", e.getMessage());
}
}
private static Bitmap resize(Bitmap image, int maxWidth, int maxHeight) {
if (maxHeight > 0 && maxWidth > 0) {
int width = image.getWidth();
int height = image.getHeight();
float ratioBitmap = (float) width / (float) height;
float ratioMax = (float) maxWidth / (float) maxHeight;
int finalWidth = maxWidth;
int finalHeight = maxHeight;
if (ratioMax > ratioBitmap) {
finalWidth = (int) ((float) maxHeight * ratioBitmap);
} else {
finalHeight = (int) ((float) maxWidth / ratioBitmap);
}
image = Bitmap.createScaledBitmap(image, finalWidth, finalHeight, true);
}
return image;
}
}