├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── photopaint │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── photopaint │ │ │ ├── MainActivity.java │ │ │ ├── helpers │ │ │ ├── AndroidUtilities.java │ │ │ ├── ApplicationLoader.java │ │ │ ├── Bitmaps.java │ │ │ ├── BuildVars.java │ │ │ ├── DispatchQueue.java │ │ │ ├── FileLog.java │ │ │ ├── Utilities.java │ │ │ └── time │ │ │ │ ├── DateParser.java │ │ │ │ ├── DatePrinter.java │ │ │ │ ├── FastDateFormat.java │ │ │ │ ├── FastDateParser.java │ │ │ │ ├── FastDatePrinter.java │ │ │ │ ├── FormatCache.java │ │ │ │ └── SunDate.java │ │ │ └── views │ │ │ ├── actionbar │ │ │ ├── ActionBarPopupWindow.java │ │ │ ├── AlertDialog.java │ │ │ └── Theme.java │ │ │ └── components │ │ │ ├── LayoutHelper.java │ │ │ ├── LineProgressView.java │ │ │ ├── PhotoPaintView.java │ │ │ ├── Point.java │ │ │ ├── RadialProgressView.java │ │ │ ├── Rect.java │ │ │ ├── Size.java │ │ │ ├── TypefaceSpan.java │ │ │ └── paint │ │ │ ├── Brush.java │ │ │ ├── GLMatrix.java │ │ │ ├── Input.java │ │ │ ├── Painting.java │ │ │ ├── Path.java │ │ │ ├── PhotoFace.java │ │ │ ├── Point.java │ │ │ ├── Render.java │ │ │ ├── RenderState.java │ │ │ ├── RenderView.java │ │ │ ├── Shader.java │ │ │ ├── ShaderSet.java │ │ │ ├── Slice.java │ │ │ ├── Swatch.java │ │ │ ├── Texture.java │ │ │ ├── UndoStore.java │ │ │ ├── Utils.java │ │ │ └── views │ │ │ ├── ColorPicker.java │ │ │ ├── EditTextOutline.java │ │ │ ├── EntitiesContainerView.java │ │ │ ├── EntityView.java │ │ │ ├── RotationGestureDetector.java │ │ │ ├── StickerView.java │ │ │ └── TextPaintView.java │ └── res │ │ ├── anim │ │ ├── popup_in.xml │ │ └── popup_out.xml │ │ ├── drawable-hdpi │ │ ├── header_shadow.png │ │ ├── header_shadow_reverse.png │ │ ├── ic_ab_done.png │ │ ├── knob_shadow.png │ │ ├── media_doc_blue.png │ │ ├── media_doc_blue_b.png │ │ ├── media_doc_green.png │ │ ├── media_doc_green_b.png │ │ ├── media_doc_load.png │ │ ├── media_doc_pause.png │ │ ├── media_doc_red.png │ │ ├── media_doc_red_b.png │ │ ├── media_doc_yellow.png │ │ ├── media_doc_yellow_b.png │ │ ├── photo_flip.png │ │ ├── photo_mosaic.png │ │ ├── photo_outline.png │ │ ├── photo_paint.png │ │ ├── photo_paint_brush.png │ │ ├── photo_paint_text.png │ │ ├── photo_recover.png │ │ ├── photo_sticker.png │ │ ├── photo_undo.png │ │ ├── popup_fixed_alert.9.png │ │ ├── popup_fixed_alert2.9.png │ │ └── popup_fixed_top.9.png │ │ ├── drawable-mdpi │ │ ├── header_shadow.png │ │ ├── header_shadow_reverse.png │ │ ├── ic_ab_done.png │ │ ├── knob_shadow.png │ │ ├── media_doc_blue.png │ │ ├── media_doc_blue_b.png │ │ ├── media_doc_green.png │ │ ├── media_doc_green_b.png │ │ ├── media_doc_load.png │ │ ├── media_doc_pause.png │ │ ├── media_doc_red.png │ │ ├── media_doc_red_b.png │ │ ├── media_doc_yellow.png │ │ ├── media_doc_yellow_b.png │ │ ├── photo_flip.png │ │ ├── photo_mosaic.png │ │ ├── photo_outline.png │ │ ├── photo_paint.png │ │ ├── photo_paint_brush.png │ │ ├── photo_paint_text.png │ │ ├── photo_recover.png │ │ ├── photo_sticker.png │ │ ├── photo_undo.png │ │ ├── popup_fixed_alert.9.png │ │ ├── popup_fixed_alert2.9.png │ │ └── popup_fixed_top.9.png │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable-xhdpi │ │ ├── header_shadow.png │ │ ├── header_shadow_reverse.png │ │ ├── ic_ab_done.png │ │ ├── knob_shadow.png │ │ ├── media_doc_blue.png │ │ ├── media_doc_blue_b.png │ │ ├── media_doc_green.png │ │ ├── media_doc_green_b.png │ │ ├── media_doc_load.png │ │ ├── media_doc_pause.png │ │ ├── media_doc_red.png │ │ ├── media_doc_red_b.png │ │ ├── media_doc_yellow.png │ │ ├── media_doc_yellow_b.png │ │ ├── photo_flip.png │ │ ├── photo_mosaic.png │ │ ├── photo_outline.png │ │ ├── photo_paint.png │ │ ├── photo_paint_brush.png │ │ ├── photo_paint_text.png │ │ ├── photo_recover.png │ │ ├── photo_sticker.png │ │ ├── photo_undo.png │ │ ├── popup_fixed_alert.9.png │ │ ├── popup_fixed_alert2.9.png │ │ └── popup_fixed_top.9.png │ │ ├── drawable-xxhdpi │ │ ├── adv_img_2.png │ │ ├── header_shadow.png │ │ ├── header_shadow_reverse.png │ │ ├── ic_ab_done.png │ │ ├── knob_shadow.png │ │ ├── media_doc_blue.png │ │ ├── media_doc_blue_b.png │ │ ├── media_doc_green.png │ │ ├── media_doc_green_b.png │ │ ├── media_doc_load.png │ │ ├── media_doc_pause.png │ │ ├── media_doc_red.png │ │ ├── media_doc_red_b.png │ │ ├── media_doc_yellow.png │ │ ├── media_doc_yellow_b.png │ │ ├── photo_flip.png │ │ ├── photo_mosaic.png │ │ ├── photo_outline.png │ │ ├── photo_paint.png │ │ ├── photo_paint_brush.png │ │ ├── photo_paint_text.png │ │ ├── photo_recover.png │ │ ├── photo_sticker.png │ │ ├── photo_undo.png │ │ ├── popup_fixed_alert.9.png │ │ ├── popup_fixed_alert2.9.png │ │ └── popup_fixed_top.9.png │ │ ├── drawable │ │ ├── adv_img_2.png │ │ ├── catstile.jpg │ │ ├── field_carret_empty.xml │ │ ├── ic_launcher_background.xml │ │ ├── mosaic_black.png │ │ ├── mosaic_white.png │ │ ├── paint_elliptical_brush.png │ │ ├── paint_elliptical_preview.png │ │ ├── paint_neon_brush.png │ │ ├── paint_neon_preview.png │ │ ├── paint_radial_brush.png │ │ ├── paint_radial_preview.png │ │ ├── screenshot.png │ │ ├── sticker_demo.png │ │ └── transparent.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── example │ └── photopaint │ └── ExampleUnitTest.kt ├── build.gradle ├── gifs ├── 1571733722289.gif ├── 1571733767440.gif ├── 1571733806801.gif ├── 1571733849769.gif └── 1571733896635.gif ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PhotoPaint 2 | 1.基于OpenGL技术的图片编辑功能模块(包含画笔涂鸦,贴纸,贴文字和画刷风格的马赛克),交互体验也很丰富。 3 | ![画笔功能](https://github.com/Rave-Tian/PhotoPaint/blob/enigma/gifs/1571733722289.gif) 4 | ![画刷风格的马赛克](https://github.com/Rave-Tian/PhotoPaint/blob/enigma/gifs/1571733767440.gif) 5 | ![文字涂鸦](https://github.com/Rave-Tian/PhotoPaint/blob/enigma/gifs/1571733806801.gif) 6 | ![图标涂鸦](https://github.com/Rave-Tian/PhotoPaint/blob/enigma/gifs/1571733849769.gif) 7 | ![撤销和重置](https://github.com/Rave-Tian/PhotoPaint/blob/enigma/gifs/1571733896635.gif) 8 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion 28 9 | defaultConfig { 10 | applicationId "com.example.photopaint" 11 | minSdkVersion 15 12 | targetSdkVersion 28 13 | versionCode 1 14 | versionName "1.0" 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | configurations { 26 | compile.exclude module: 'support-v4' 27 | } 28 | 29 | dependencies { 30 | implementation 'androidx.core:core:1.1.0-beta01' 31 | implementation 'androidx.palette:palette:1.0.0' 32 | implementation 'androidx.exifinterface:exifinterface:1.0.0' 33 | 34 | implementation fileTree(dir: 'libs', include: ['*.jar']) 35 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 36 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 37 | implementation 'com.google.android.gms:play-services-maps:16.0.0' 38 | implementation 'com.google.android.gms:play-services-auth:16.0.1' 39 | implementation 'com.google.android.gms:play-services-vision:16.2.0' 40 | implementation 'com.google.android.gms:play-services-wallet:16.0.1' 41 | implementation 'com.google.android.gms:play-services-wearable:16.0.1' 42 | } 43 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/photopaint/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.photopaint 2 | 3 | import android.support.test.InstrumentationRegistry 4 | import android.support.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | assertEquals("com.example.photopaint", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 14 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint; 2 | 3 | import android.app.Activity; 4 | import android.graphics.BitmapFactory; 5 | import android.graphics.Color; 6 | import android.os.Bundle; 7 | import android.view.Gravity; 8 | import android.view.View; 9 | import android.widget.Button; 10 | import android.widget.FrameLayout; 11 | import com.example.photopaint.views.components.LayoutHelper; 12 | import com.example.photopaint.views.components.PhotoPaintView; 13 | 14 | public class MainActivity extends Activity { 15 | private FrameLayout frameLayout; 16 | private PhotoPaintView photoPaintView; 17 | 18 | @Override 19 | protected void onCreate(Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | 22 | frameLayout = new FrameLayout(this); 23 | frameLayout.setBackgroundColor(Color.GRAY); 24 | setContentView(frameLayout); 25 | 26 | photoPaintView = new PhotoPaintView(MainActivity.this, BitmapFactory.decodeResource(getResources(), R.drawable.adv_img_2), 0); 27 | frameLayout.addView(photoPaintView, new FrameLayout.LayoutParams(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); 28 | photoPaintView.init(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/helpers/ApplicationLoader.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.helpers; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Application; 5 | import android.content.Context; 6 | import android.os.Handler; 7 | 8 | public class ApplicationLoader extends Application { 9 | 10 | @SuppressLint("StaticFieldLeak") 11 | public static volatile Context applicationContext; 12 | public static volatile Handler applicationHandler; 13 | 14 | public ApplicationLoader() { 15 | super(); 16 | } 17 | 18 | @Override 19 | public void onCreate() { 20 | try { 21 | applicationContext = getApplicationContext(); 22 | } catch (Throwable ignore) { 23 | 24 | } 25 | super.onCreate(); 26 | 27 | if (applicationContext == null) { 28 | applicationContext = getApplicationContext(); 29 | } 30 | 31 | applicationHandler = new Handler(applicationContext.getMainLooper()); 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/helpers/BuildVars.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.helpers; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | public class BuildVars { 7 | 8 | public static boolean DEBUG_VERSION = false; 9 | public static boolean DEBUG_PRIVATE_VERSION = false; 10 | public static boolean LOGS_ENABLED = false; 11 | public static boolean USE_CLOUD_STRINGS = true; 12 | public static boolean CHECK_UPDATES = false; 13 | public static int BUILD_VERSION = 1608; 14 | public static String BUILD_VERSION_STRING = "5.7.0"; 15 | public static int APP_ID = 800906; //obtain your own APP_ID at https://core.telegram.org/api/obtaining_api_id 16 | public static String APP_HASH = "ff0354a8224b666e3486875143ce0278"; //obtain your own APP_HASH at https://core.telegram.org/api/obtaining_api_id 17 | public static String HOCKEY_APP_HASH = "00000000000000000000000000000000"; 18 | public static String HOCKEY_APP_HASH_DEBUG = "00000000000000000000000000000000"; 19 | public static String SMS_HASH = ""; //https://developers.google.com/identity/sms-retriever/overview 20 | public static String PLAYSTORE_APP_URL = ""; 21 | 22 | static { 23 | if (ApplicationLoader.applicationContext != null) { 24 | SharedPreferences sharedPreferences = ApplicationLoader.applicationContext.getSharedPreferences("systemConfig", Context.MODE_PRIVATE); 25 | LOGS_ENABLED = sharedPreferences.getBoolean("logsEnabled", DEBUG_VERSION); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/helpers/DispatchQueue.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.helpers; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | import android.os.Message; 6 | 7 | import java.util.concurrent.CountDownLatch; 8 | 9 | public class DispatchQueue extends Thread { 10 | 11 | private volatile Handler handler = null; 12 | private CountDownLatch syncLatch = new CountDownLatch(1); 13 | 14 | public DispatchQueue(final String threadName) { 15 | setName(threadName); 16 | start(); 17 | } 18 | 19 | public void sendMessage(Message msg, int delay) { 20 | try { 21 | syncLatch.await(); 22 | if (delay <= 0) { 23 | handler.sendMessage(msg); 24 | } else { 25 | handler.sendMessageDelayed(msg, delay); 26 | } 27 | } catch (Exception e) { 28 | FileLog.e(e); 29 | } 30 | } 31 | 32 | public void cancelRunnable(Runnable runnable) { 33 | try { 34 | syncLatch.await(); 35 | handler.removeCallbacks(runnable); 36 | } catch (Exception e) { 37 | FileLog.e(e); 38 | } 39 | } 40 | 41 | public void postRunnable(Runnable runnable) { 42 | postRunnable(runnable, 0); 43 | } 44 | 45 | public void postRunnable(Runnable runnable, long delay) { 46 | try { 47 | syncLatch.await(); 48 | } catch (Exception e) { 49 | FileLog.e(e); 50 | } 51 | if (delay <= 0) { 52 | handler.post(runnable); 53 | } else { 54 | handler.postDelayed(runnable, delay); 55 | } 56 | } 57 | 58 | public void cleanupQueue() { 59 | try { 60 | syncLatch.await(); 61 | handler.removeCallbacksAndMessages(null); 62 | } catch (Exception e) { 63 | FileLog.e(e); 64 | } 65 | } 66 | 67 | public void handleMessage(Message inputMessage) { 68 | 69 | } 70 | 71 | public void recycle() { 72 | handler.getLooper().quit(); 73 | } 74 | 75 | @Override 76 | public void run() { 77 | Looper.prepare(); 78 | handler = new Handler() { 79 | @Override 80 | public void handleMessage(Message msg) { 81 | DispatchQueue.this.handleMessage(msg); 82 | } 83 | }; 84 | syncLatch.countDown(); 85 | Looper.loop(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/helpers/FileLog.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.helpers; 2 | 3 | import android.util.Log; 4 | import com.example.photopaint.helpers.time.FastDateFormat; 5 | 6 | import java.io.File; 7 | import java.io.FileOutputStream; 8 | import java.io.OutputStreamWriter; 9 | import java.util.Locale; 10 | 11 | public class FileLog { 12 | private OutputStreamWriter streamWriter = null; 13 | private FastDateFormat dateFormat = null; 14 | private DispatchQueue logQueue = null; 15 | private File currentFile = null; 16 | private File networkFile = null; 17 | private boolean initied; 18 | 19 | private final static String tag = "tmessages"; 20 | 21 | private static volatile FileLog Instance = null; 22 | 23 | public static FileLog getInstance() { 24 | FileLog localInstance = Instance; 25 | if (localInstance == null) { 26 | synchronized (FileLog.class) { 27 | localInstance = Instance; 28 | if (localInstance == null) { 29 | Instance = localInstance = new FileLog(); 30 | } 31 | } 32 | } 33 | return localInstance; 34 | } 35 | 36 | public FileLog() { 37 | if (!BuildVars.LOGS_ENABLED) { 38 | return; 39 | } 40 | init(); 41 | } 42 | 43 | public void init() { 44 | if (initied) { 45 | return; 46 | } 47 | dateFormat = FastDateFormat.getInstance("dd_MM_yyyy_HH_mm_ss", Locale.US); 48 | try { 49 | File sdCard = ApplicationLoader.applicationContext.getExternalFilesDir(null); 50 | if (sdCard == null) { 51 | return; 52 | } 53 | File dir = new File(sdCard.getAbsolutePath() + "/logs"); 54 | dir.mkdirs(); 55 | currentFile = new File(dir, dateFormat.format(System.currentTimeMillis()) + ".txt"); 56 | } catch (Exception e) { 57 | e.printStackTrace(); 58 | } 59 | try { 60 | logQueue = new DispatchQueue("logQueue"); 61 | currentFile.createNewFile(); 62 | FileOutputStream stream = new FileOutputStream(currentFile); 63 | streamWriter = new OutputStreamWriter(stream); 64 | streamWriter.write("-----start log " + dateFormat.format(System.currentTimeMillis()) + "-----\n"); 65 | streamWriter.flush(); 66 | } catch (Exception e) { 67 | e.printStackTrace(); 68 | } 69 | initied = true; 70 | } 71 | 72 | public static void ensureInitied() { 73 | getInstance().init(); 74 | } 75 | 76 | public static String getNetworkLogPath() { 77 | if (!BuildVars.LOGS_ENABLED) { 78 | return ""; 79 | } 80 | try { 81 | File sdCard = ApplicationLoader.applicationContext.getExternalFilesDir(null); 82 | if (sdCard == null) { 83 | return ""; 84 | } 85 | File dir = new File(sdCard.getAbsolutePath() + "/logs"); 86 | dir.mkdirs(); 87 | getInstance().networkFile = new File(dir, getInstance().dateFormat.format(System.currentTimeMillis()) + "_net.txt"); 88 | return getInstance().networkFile.getAbsolutePath(); 89 | } catch (Throwable e) { 90 | e.printStackTrace(); 91 | } 92 | return ""; 93 | } 94 | 95 | public static void e(final String message, final Throwable exception) { 96 | if (!BuildVars.LOGS_ENABLED) { 97 | return; 98 | } 99 | ensureInitied(); 100 | Log.e(tag, message, exception); 101 | if (getInstance().streamWriter != null) { 102 | getInstance().logQueue.postRunnable(new Runnable() { 103 | @Override 104 | public void run() { 105 | try { 106 | getInstance().streamWriter.write(getInstance().dateFormat.format(System.currentTimeMillis()) + " E/tmessages: " + message + "\n"); 107 | getInstance().streamWriter.write(exception.toString()); 108 | getInstance().streamWriter.flush(); 109 | } catch (Exception e) { 110 | e.printStackTrace(); 111 | } 112 | } 113 | }); 114 | } 115 | } 116 | 117 | public static void e(final String message) { 118 | if (!BuildVars.LOGS_ENABLED) { 119 | return; 120 | } 121 | ensureInitied(); 122 | Log.e(tag, message); 123 | if (getInstance().streamWriter != null) { 124 | getInstance().logQueue.postRunnable(new Runnable() { 125 | @Override 126 | public void run() { 127 | try { 128 | getInstance().streamWriter.write(getInstance().dateFormat.format(System.currentTimeMillis()) + " E/tmessages: " + message + "\n"); 129 | getInstance().streamWriter.flush(); 130 | } catch (Exception e) { 131 | e.printStackTrace(); 132 | } 133 | } 134 | }); 135 | } 136 | } 137 | 138 | public static void e(final Throwable e) { 139 | if (!BuildVars.LOGS_ENABLED) { 140 | return; 141 | } 142 | ensureInitied(); 143 | e.printStackTrace(); 144 | if (getInstance().streamWriter != null) { 145 | 146 | getInstance().logQueue.postRunnable(new Runnable() { 147 | @Override 148 | public void run() { 149 | try { 150 | getInstance().streamWriter.write(getInstance().dateFormat.format(System.currentTimeMillis()) + " E/tmessages: " + e + "\n"); 151 | StackTraceElement[] stack = e.getStackTrace(); 152 | for (int a = 0; a < stack.length; a++) { 153 | getInstance().streamWriter.write(getInstance().dateFormat.format(System.currentTimeMillis()) + " E/tmessages: " + stack[a] + "\n"); 154 | } 155 | getInstance().streamWriter.flush(); 156 | } catch (Exception e1) { 157 | e1.printStackTrace(); 158 | } 159 | } 160 | }); 161 | } else { 162 | e.printStackTrace(); 163 | } 164 | } 165 | 166 | public static void d(final String message) { 167 | if (!BuildVars.LOGS_ENABLED) { 168 | return; 169 | } 170 | ensureInitied(); 171 | Log.d(tag, message); 172 | if (getInstance().streamWriter != null) { 173 | getInstance().logQueue.postRunnable(new Runnable() { 174 | @Override 175 | public void run() { 176 | try { 177 | getInstance().streamWriter.write(getInstance().dateFormat.format(System.currentTimeMillis()) + " D/tmessages: " + message + "\n"); 178 | getInstance().streamWriter.flush(); 179 | } catch (Exception e) { 180 | e.printStackTrace(); 181 | } 182 | } 183 | }); 184 | } 185 | } 186 | 187 | public static void w(final String message) { 188 | if (!BuildVars.LOGS_ENABLED) { 189 | return; 190 | } 191 | ensureInitied(); 192 | Log.w(tag, message); 193 | if (getInstance().streamWriter != null) { 194 | getInstance().logQueue.postRunnable(new Runnable() { 195 | @Override 196 | public void run() { 197 | try { 198 | getInstance().streamWriter.write(getInstance().dateFormat.format(System.currentTimeMillis()) + " W/tmessages: " + message + "\n"); 199 | getInstance().streamWriter.flush(); 200 | } catch (Exception e) { 201 | e.printStackTrace(); 202 | } 203 | } 204 | }); 205 | } 206 | } 207 | 208 | public static void cleanupLogs() { 209 | ensureInitied(); 210 | File sdCard = ApplicationLoader.applicationContext.getExternalFilesDir(null); 211 | if (sdCard == null) { 212 | return; 213 | } 214 | File dir = new File(sdCard.getAbsolutePath() + "/logs"); 215 | File[] files = dir.listFiles(); 216 | if (files != null) { 217 | for (int a = 0; a < files.length; a++) { 218 | File file = files[a]; 219 | if (getInstance().currentFile != null && file.getAbsolutePath().equals(getInstance().currentFile.getAbsolutePath())) { 220 | continue; 221 | } 222 | if (getInstance().networkFile != null && file.getAbsolutePath().equals(getInstance().networkFile.getAbsolutePath())) { 223 | continue; 224 | } 225 | file.delete(); 226 | } 227 | } 228 | } 229 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/helpers/time/DateParser.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.helpers.time; 2 | 3 | import java.text.ParseException; 4 | import java.text.ParsePosition; 5 | import java.util.Date; 6 | import java.util.Locale; 7 | import java.util.TimeZone; 8 | 9 | public interface DateParser { 10 | 11 | /** 12 | * Equivalent to DateFormat.parse(String). 13 | *

14 | * See {@link java.text.DateFormat#parse(String)} for more information. 15 | * 16 | * @param source A String whose beginning should be parsed. 17 | * @return A Date parsed from the string 18 | * @throws ParseException if the beginning of the specified string cannot be parsed. 19 | */ 20 | Date parse(String source) throws ParseException; 21 | 22 | /** 23 | * Equivalent to DateFormat.parse(String, ParsePosition). 24 | *

25 | * See {@link java.text.DateFormat#parse(String, ParsePosition)} for more information. 26 | * 27 | * @param source A String, part of which should be parsed. 28 | * @param pos A ParsePosition object with index and error index information 29 | * as described above. 30 | * @return A Date parsed from the string. In case of error, returns null. 31 | * @throws NullPointerException if text or pos is null. 32 | */ 33 | Date parse(String source, ParsePosition pos); 34 | 35 | // Accessors 36 | //----------------------------------------------------------------------- 37 | 38 | /** 39 | *

Get the pattern used by this parser.

40 | * 41 | * @return the pattern, {@link java.text.SimpleDateFormat} compatible 42 | */ 43 | String getPattern(); 44 | 45 | /** 46 | *

47 | * Get the time zone used by this parser. 48 | *

49 | *

50 | *

51 | * The default {@link TimeZone} used to create a {@link Date} when the {@link TimeZone} is not specified by 52 | * the format pattern. 53 | *

54 | * 55 | * @return the time zone 56 | */ 57 | TimeZone getTimeZone(); 58 | 59 | /** 60 | *

Get the locale used by this parser.

61 | * 62 | * @return the locale 63 | */ 64 | Locale getLocale(); 65 | 66 | /** 67 | * Parses text from a string to produce a Date. 68 | * 69 | * @param source A String whose beginning should be parsed. 70 | * @return a java.util.Date object 71 | * @throws ParseException if the beginning of the specified string cannot be parsed. 72 | * @see java.text.DateFormat#parseObject(String) 73 | */ 74 | Object parseObject(String source) throws ParseException; 75 | 76 | /** 77 | * Parse a date/time string according to the given parse position. 78 | * 79 | * @param source A String whose beginning should be parsed. 80 | * @param pos the parse position 81 | * @return a java.util.Date object 82 | * @see java.text.DateFormat#parseObject(String, ParsePosition) 83 | */ 84 | Object parseObject(String source, ParsePosition pos); 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/helpers/time/DatePrinter.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.helpers.time; 2 | 3 | import java.text.FieldPosition; 4 | import java.util.Calendar; 5 | import java.util.Date; 6 | import java.util.Locale; 7 | import java.util.TimeZone; 8 | 9 | public interface DatePrinter { 10 | 11 | /** 12 | *

Formats a millisecond {@code long} value.

13 | * 14 | * @param millis the millisecond value to format 15 | * @return the formatted string 16 | * @since 2.1 17 | */ 18 | String format(long millis); 19 | 20 | /** 21 | *

Formats a {@code Date} object using a {@code GregorianCalendar}.

22 | * 23 | * @param date the date to format 24 | * @return the formatted string 25 | */ 26 | String format(Date date); 27 | 28 | /** 29 | *

Formats a {@code Calendar} object.

30 | * 31 | * @param calendar the calendar to format 32 | * @return the formatted string 33 | */ 34 | String format(Calendar calendar); 35 | 36 | /** 37 | *

Formats a milliseond {@code long} value into the 38 | * supplied {@code StringBuffer}.

39 | * 40 | * @param millis the millisecond value to format 41 | * @param buf the buffer to format into 42 | * @return the specified string buffer 43 | */ 44 | StringBuffer format(long millis, StringBuffer buf); 45 | 46 | /** 47 | *

Formats a {@code Date} object into the 48 | * supplied {@code StringBuffer} using a {@code GregorianCalendar}.

49 | * 50 | * @param date the date to format 51 | * @param buf the buffer to format into 52 | * @return the specified string buffer 53 | */ 54 | StringBuffer format(Date date, StringBuffer buf); 55 | 56 | /** 57 | *

Formats a {@code Calendar} object into the 58 | * supplied {@code StringBuffer}.

59 | * 60 | * @param calendar the calendar to format 61 | * @param buf the buffer to format into 62 | * @return the specified string buffer 63 | */ 64 | StringBuffer format(Calendar calendar, StringBuffer buf); 65 | 66 | // Accessors 67 | //----------------------------------------------------------------------- 68 | 69 | /** 70 | *

Gets the pattern used by this printer.

71 | * 72 | * @return the pattern, {@link java.text.SimpleDateFormat} compatible 73 | */ 74 | String getPattern(); 75 | 76 | /** 77 | *

Gets the time zone used by this printer.

78 | *

79 | *

This zone is always used for {@code Date} printing.

80 | * 81 | * @return the time zone 82 | */ 83 | TimeZone getTimeZone(); 84 | 85 | /** 86 | *

Gets the locale used by this printer.

87 | * 88 | * @return the locale 89 | */ 90 | Locale getLocale(); 91 | 92 | /** 93 | *

Formats a {@code Date}, {@code Calendar} or 94 | * {@code Long} (milliseconds) object.

95 | *

96 | * See {@link java.text.DateFormat#format(Object, StringBuffer, FieldPosition)} 97 | * 98 | * @param obj the object to format 99 | * @param toAppendTo the buffer to append to 100 | * @param pos the position - ignored 101 | * @return the buffer passed in 102 | */ 103 | StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos); 104 | } 105 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/helpers/time/FormatCache.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.helpers.time; 2 | 3 | import java.text.DateFormat; 4 | import java.text.Format; 5 | import java.text.SimpleDateFormat; 6 | import java.util.Arrays; 7 | import java.util.Locale; 8 | import java.util.TimeZone; 9 | import java.util.concurrent.ConcurrentHashMap; 10 | import java.util.concurrent.ConcurrentMap; 11 | 12 | 13 | abstract class FormatCache { 14 | /** 15 | * No date or no time. Used in same parameters as DateFormat.SHORT or DateFormat.LONG 16 | */ 17 | static final int NONE = -1; 18 | 19 | private final ConcurrentMap cInstanceCache 20 | = new ConcurrentHashMap(7); 21 | 22 | private static final ConcurrentMap cDateTimeInstanceCache 23 | = new ConcurrentHashMap(7); 24 | 25 | /** 26 | *

Gets a formatter instance using the default pattern in the 27 | * default timezone and locale.

28 | * 29 | * @return a date/time formatter 30 | */ 31 | public F getInstance() { 32 | return getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, TimeZone.getDefault(), Locale.getDefault()); 33 | } 34 | 35 | /** 36 | *

Gets a formatter instance using the specified pattern, time zone 37 | * and locale.

38 | * 39 | * @param pattern {@link SimpleDateFormat} compatible 40 | * pattern, non-null 41 | * @param timeZone the time zone, null means use the default TimeZone 42 | * @param locale the locale, null means use the default Locale 43 | * @return a pattern based date/time formatter 44 | * @throws IllegalArgumentException if pattern is invalid 45 | * or null 46 | */ 47 | public F getInstance(final String pattern, TimeZone timeZone, Locale locale) { 48 | if (pattern == null) { 49 | throw new NullPointerException("pattern must not be null"); 50 | } 51 | if (timeZone == null) { 52 | timeZone = TimeZone.getDefault(); 53 | } 54 | if (locale == null) { 55 | locale = Locale.getDefault(); 56 | } 57 | final MultipartKey key = new MultipartKey(pattern, timeZone, locale); 58 | F format = cInstanceCache.get(key); 59 | if (format == null) { 60 | format = createInstance(pattern, timeZone, locale); 61 | final F previousValue = cInstanceCache.putIfAbsent(key, format); 62 | if (previousValue != null) { 63 | // another thread snuck in and did the same work 64 | // we should return the instance that is in ConcurrentMap 65 | format = previousValue; 66 | } 67 | } 68 | return format; 69 | } 70 | 71 | /** 72 | *

Create a format instance using the specified pattern, time zone 73 | * and locale.

74 | * 75 | * @param pattern {@link SimpleDateFormat} compatible pattern, this will not be null. 76 | * @param timeZone time zone, this will not be null. 77 | * @param locale locale, this will not be null. 78 | * @return a pattern based date/time formatter 79 | * @throws IllegalArgumentException if pattern is invalid 80 | * or null 81 | */ 82 | abstract protected F createInstance(String pattern, TimeZone timeZone, Locale locale); 83 | 84 | /** 85 | *

Gets a date/time formatter instance using the specified style, 86 | * time zone and locale.

87 | * 88 | * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT, null indicates no date in format 89 | * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT, null indicates no time in format 90 | * @param timeZone optional time zone, overrides time zone of 91 | * formatted date, null means use default Locale 92 | * @param locale optional locale, overrides system locale 93 | * @return a localized standard date/time formatter 94 | * @throws IllegalArgumentException if the Locale has no date/time 95 | * pattern defined 96 | */ 97 | // This must remain private, see LANG-884 98 | private F getDateTimeInstance(final Integer dateStyle, final Integer timeStyle, final TimeZone timeZone, Locale locale) { 99 | if (locale == null) { 100 | locale = Locale.getDefault(); 101 | } 102 | final String pattern = getPatternForStyle(dateStyle, timeStyle, locale); 103 | return getInstance(pattern, timeZone, locale); 104 | } 105 | 106 | /** 107 | *

Gets a date/time formatter instance using the specified style, 108 | * time zone and locale.

109 | * 110 | * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT 111 | * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT 112 | * @param timeZone optional time zone, overrides time zone of 113 | * formatted date, null means use default Locale 114 | * @param locale optional locale, overrides system locale 115 | * @return a localized standard date/time formatter 116 | * @throws IllegalArgumentException if the Locale has no date/time 117 | * pattern defined 118 | */ 119 | // package protected, for access from FastDateFormat; do not make public or protected 120 | F getDateTimeInstance(final int dateStyle, final int timeStyle, final TimeZone timeZone, Locale locale) { 121 | return getDateTimeInstance(Integer.valueOf(dateStyle), Integer.valueOf(timeStyle), timeZone, locale); 122 | } 123 | 124 | /** 125 | *

Gets a date formatter instance using the specified style, 126 | * time zone and locale.

127 | * 128 | * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT 129 | * @param timeZone optional time zone, overrides time zone of 130 | * formatted date, null means use default Locale 131 | * @param locale optional locale, overrides system locale 132 | * @return a localized standard date/time formatter 133 | * @throws IllegalArgumentException if the Locale has no date/time 134 | * pattern defined 135 | */ 136 | // package protected, for access from FastDateFormat; do not make public or protected 137 | F getDateInstance(final int dateStyle, final TimeZone timeZone, Locale locale) { 138 | return getDateTimeInstance(Integer.valueOf(dateStyle), null, timeZone, locale); 139 | } 140 | 141 | /** 142 | *

Gets a time formatter instance using the specified style, 143 | * time zone and locale.

144 | * 145 | * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT 146 | * @param timeZone optional time zone, overrides time zone of 147 | * formatted date, null means use default Locale 148 | * @param locale optional locale, overrides system locale 149 | * @return a localized standard date/time formatter 150 | * @throws IllegalArgumentException if the Locale has no date/time 151 | * pattern defined 152 | */ 153 | // package protected, for access from FastDateFormat; do not make public or protected 154 | F getTimeInstance(final int timeStyle, final TimeZone timeZone, Locale locale) { 155 | return getDateTimeInstance(null, Integer.valueOf(timeStyle), timeZone, locale); 156 | } 157 | 158 | /** 159 | *

Gets a date/time format for the specified styles and locale.

160 | * 161 | * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT, null indicates no date in format 162 | * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT, null indicates no time in format 163 | * @param locale The non-null locale of the desired format 164 | * @return a localized standard date/time format 165 | * @throws IllegalArgumentException if the Locale has no date/time pattern defined 166 | */ 167 | // package protected, for access from test code; do not make public or protected 168 | static String getPatternForStyle(final Integer dateStyle, final Integer timeStyle, final Locale locale) { 169 | final MultipartKey key = new MultipartKey(dateStyle, timeStyle, locale); 170 | 171 | String pattern = cDateTimeInstanceCache.get(key); 172 | if (pattern == null) { 173 | try { 174 | DateFormat formatter; 175 | if (dateStyle == null) { 176 | formatter = DateFormat.getTimeInstance(timeStyle.intValue(), locale); 177 | } else if (timeStyle == null) { 178 | formatter = DateFormat.getDateInstance(dateStyle.intValue(), locale); 179 | } else { 180 | formatter = DateFormat.getDateTimeInstance(dateStyle.intValue(), timeStyle.intValue(), locale); 181 | } 182 | pattern = ((SimpleDateFormat) formatter).toPattern(); 183 | final String previous = cDateTimeInstanceCache.putIfAbsent(key, pattern); 184 | if (previous != null) { 185 | // even though it doesn't matter if another thread put the pattern 186 | // it's still good practice to return the String instance that is 187 | // actually in the ConcurrentMap 188 | pattern = previous; 189 | } 190 | } catch (final ClassCastException ex) { 191 | throw new IllegalArgumentException("No date time pattern for locale: " + locale); 192 | } 193 | } 194 | return pattern; 195 | } 196 | 197 | // ---------------------------------------------------------------------- 198 | 199 | /** 200 | *

Helper class to hold multi-part Map keys

201 | */ 202 | private static class MultipartKey { 203 | private final Object[] keys; 204 | private int hashCode; 205 | 206 | /** 207 | * Constructs an instance of MultipartKey to hold the specified objects. 208 | * 209 | * @param keys the set of objects that make up the key. Each key may be null. 210 | */ 211 | public MultipartKey(final Object... keys) { 212 | this.keys = keys; 213 | } 214 | 215 | /** 216 | * {@inheritDoc} 217 | */ 218 | @Override 219 | public boolean equals(final Object obj) { 220 | // Eliminate the usual boilerplate because 221 | // this inner static class is only used in a generic ConcurrentHashMap 222 | // which will not compare against other Object types 223 | return Arrays.equals(keys, ((MultipartKey) obj).keys); 224 | } 225 | 226 | /** 227 | * {@inheritDoc} 228 | */ 229 | @Override 230 | public int hashCode() { 231 | if (hashCode == 0) { 232 | int rc = 0; 233 | for (final Object key : keys) { 234 | if (key != null) { 235 | rc = rc * 7 + key.hashCode(); 236 | } 237 | } 238 | hashCode = rc; 239 | } 240 | return hashCode; 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/helpers/time/SunDate.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.helpers.time; 2 | 3 | import java.util.Calendar; 4 | import java.util.TimeZone; 5 | 6 | public class SunDate { 7 | 8 | private static final double DEGRAD = Math.PI / 180.0; 9 | private static final double RADEG = 180.0 / Math.PI; 10 | private static final double INV360 = 1.0 / 360.0; 11 | 12 | private static long days_since_2000_Jan_0(int y, int m, int d) { 13 | return 367L * y - ((7 * (y + ((m + 9) / 12))) / 4) + ((275 * m) / 9) + d - 730530L; 14 | } 15 | 16 | private static double revolution(double x) { 17 | return x - 360.0 * Math.floor(x * INV360); 18 | } 19 | 20 | private static double rev180(double x) { 21 | return x - 360.0 * Math.floor(x * INV360 + 0.5); 22 | } 23 | 24 | private static double GMST0(double d) { 25 | return revolution((180.0 + 356.0470 + 282.9404) + (0.9856002585 + 4.70935E-5) * d); 26 | } 27 | 28 | private static double sind(double x) { 29 | return Math.sin(x * DEGRAD); 30 | } 31 | 32 | private static double cosd(double x) { 33 | return Math.cos(x * DEGRAD); 34 | } 35 | 36 | private static double tand(double x) { 37 | return Math.tan(x * DEGRAD); 38 | } 39 | 40 | private static double acosd(double x) { 41 | return RADEG * Math.acos(x); 42 | } 43 | 44 | private static double atan2d(double y, double x) { 45 | return RADEG * Math.atan2(y, x); 46 | } 47 | 48 | private static void sunposAtDay(double p, double[] ot, double[] d) { 49 | double S, a, V, l, k, i; 50 | 51 | S = revolution(356.0470 + 0.9856002585 * p); 52 | l = 282.9404 + 4.70935E-5 * p; 53 | a = 0.016709 - 1.151E-9 * p; 54 | 55 | V = a * RADEG * sind(S) * (1.0 + a * cosd(S)) + S; 56 | k = cosd(V) - a; 57 | 58 | i = Math.sqrt(1.0 - a * a) * sind(V); 59 | d[0] = Math.sqrt(k * k + i * i); 60 | i = atan2d(i, k); 61 | ot[0] = i + l; 62 | if (ot[0] >= 360.0) { 63 | ot[0] -= 360.0; 64 | } 65 | } 66 | 67 | private static void sun_RA_decAtDay(double d, double[] RA, double[] dec, double[] r) { 68 | double[] lon = new double[1]; 69 | double obl_ecl; 70 | double xs, ys; 71 | double xe, ye, ze; 72 | 73 | sunposAtDay(d, lon, r); 74 | 75 | xs = r[0] * cosd(lon[0]); 76 | ys = r[0] * sind(lon[0]); 77 | 78 | obl_ecl = 23.4393 - 3.563E-7 * d; 79 | 80 | xe = xs; 81 | ye = ys * cosd(obl_ecl); 82 | ze = ys * sind(obl_ecl); 83 | 84 | RA[0] = atan2d(ye, xe); 85 | dec[0] = atan2d(ze, Math.sqrt(xe * xe + ye * ye)); 86 | } 87 | 88 | private static int sunRiseSetHelperForYear(int year, int month, int day, double lon, double lat, double altit, int upper_limb, double[] sun) { 89 | double[] sRA = new double[1]; 90 | double[] sdec = new double[1]; 91 | double[] sr = new double[1]; 92 | 93 | double d, sradius, t, tsouth, sidtime; 94 | int rc = 0; 95 | d = days_since_2000_Jan_0(year, month, day) + 0.5 - lon / 360.0; 96 | sidtime = revolution(GMST0(d) + 180.0 + lon); 97 | sun_RA_decAtDay(d, sRA, sdec, sr); 98 | tsouth = 12.0 - rev180(sidtime - sRA[0]) / 15.0; 99 | sradius = 0.2666 / sr[0]; 100 | if (upper_limb != 0) { 101 | altit -= sradius; 102 | } 103 | 104 | double cost; 105 | cost = (sind(altit) - sind(lat) * sind(sdec[0])) / (cosd(lat) * cosd(sdec[0])); 106 | if (cost >= 1.0) { 107 | rc = -1; 108 | t = 0.0; 109 | } else if (cost <= -1.0) { 110 | rc = +1; 111 | t = 12.0; 112 | } else { 113 | t = acosd(cost) / 15.0; 114 | } 115 | sun[0] = tsouth - t; 116 | sun[1] = tsouth + t; 117 | 118 | return rc; 119 | } 120 | 121 | private static int sunRiseSetForYear(int year, int month, int day, double lon, double lat, double[] sun) { 122 | return sunRiseSetHelperForYear(year, month, day, lon, lat, (-35.0 / 60.0), 1, sun); 123 | } 124 | 125 | public static int[] calculateSunriseSunset(double lat, double lon) { 126 | Calendar calendar = Calendar.getInstance(); 127 | calendar.setTimeInMillis(System.currentTimeMillis()); 128 | double[] sun = new double[2]; 129 | sunRiseSetForYear(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.DAY_OF_MONTH), lon, lat, sun); 130 | int timeZoneOffset = TimeZone.getDefault().getOffset(System.currentTimeMillis()) / 1000 / 60; 131 | int sunrise = (int) (sun[0] * 60) + timeZoneOffset; 132 | int sunset = (int) (sun[1] * 60) + timeZoneOffset; 133 | if (sunrise < 0) { 134 | sunrise += 60 * 24; 135 | } else if (sunrise > 60 * 24) { 136 | sunrise -= 60 * 24; 137 | } 138 | if (sunset < 0) { 139 | sunset += 60 * 24; 140 | } else if (sunset > 60 * 24) { 141 | sunset += 60 * 24; 142 | } 143 | return new int[] {sunrise, sunset}; 144 | } 145 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/views/components/LayoutHelper.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.views.components; 2 | 3 | import android.widget.FrameLayout; 4 | import android.widget.LinearLayout; 5 | import android.widget.RelativeLayout; 6 | import android.widget.ScrollView; 7 | 8 | import com.example.photopaint.helpers.AndroidUtilities; 9 | 10 | public class LayoutHelper { 11 | 12 | public static final int MATCH_PARENT = -1; 13 | public static final int WRAP_CONTENT = -2; 14 | 15 | private static int getSize(float size) { 16 | return (int) (size < 0 ? size : AndroidUtilities.dp(size)); 17 | } 18 | 19 | public static ScrollView.LayoutParams createScroll(int width, int height, int gravity) { 20 | return new ScrollView.LayoutParams(getSize(width), getSize(height), gravity); 21 | } 22 | 23 | public static ScrollView.LayoutParams createScroll(int width, int height, int gravity, float leftMargin, float topMargin, float rightMargin, float bottomMargin) { 24 | ScrollView.LayoutParams layoutParams = new ScrollView.LayoutParams(getSize(width), getSize(height), gravity); 25 | layoutParams.leftMargin = AndroidUtilities.dp(leftMargin); 26 | layoutParams.topMargin = AndroidUtilities.dp(topMargin); 27 | layoutParams.rightMargin = AndroidUtilities.dp(rightMargin); 28 | layoutParams.bottomMargin = AndroidUtilities.dp(bottomMargin); 29 | return layoutParams; 30 | } 31 | 32 | public static FrameLayout.LayoutParams createFrame(int width, float height, int gravity, float leftMargin, float topMargin, float rightMargin, float bottomMargin) { 33 | FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(getSize(width), getSize(height), gravity); 34 | layoutParams.setMargins(AndroidUtilities.dp(leftMargin), AndroidUtilities.dp(topMargin), AndroidUtilities.dp(rightMargin), AndroidUtilities.dp(bottomMargin)); 35 | return layoutParams; 36 | } 37 | 38 | public static FrameLayout.LayoutParams createFrame(int width, int height, int gravity) { 39 | return new FrameLayout.LayoutParams(getSize(width), getSize(height), gravity); 40 | } 41 | 42 | public static FrameLayout.LayoutParams createFrame(int width, float height) { 43 | return new FrameLayout.LayoutParams(getSize(width), getSize(height)); 44 | } 45 | 46 | public static FrameLayout.LayoutParams createFrame(float width, float height, int gravity) { 47 | return new FrameLayout.LayoutParams(getSize(width), getSize(height), gravity); 48 | } 49 | 50 | public static RelativeLayout.LayoutParams createRelative(float width, float height, int leftMargin, int topMargin, int rightMargin, int bottomMargin, int alignParent, int alignRelative, int anchorRelative) { 51 | RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(getSize(width), getSize(height)); 52 | if (alignParent >= 0) { 53 | layoutParams.addRule(alignParent); 54 | } 55 | if (alignRelative >= 0 && anchorRelative >= 0) { 56 | layoutParams.addRule(alignRelative, anchorRelative); 57 | } 58 | layoutParams.leftMargin = AndroidUtilities.dp(leftMargin); 59 | layoutParams.topMargin = AndroidUtilities.dp(topMargin); 60 | layoutParams.rightMargin = AndroidUtilities.dp(rightMargin); 61 | layoutParams.bottomMargin = AndroidUtilities.dp(bottomMargin); 62 | return layoutParams; 63 | } 64 | 65 | public static RelativeLayout.LayoutParams createRelative(int width, int height, int leftMargin, int topMargin, int rightMargin, int bottomMargin) { 66 | return createRelative(width, height, leftMargin, topMargin, rightMargin, bottomMargin, -1, -1, -1); 67 | } 68 | 69 | public static RelativeLayout.LayoutParams createRelative(int width, int height, int leftMargin, int topMargin, int rightMargin, int bottomMargin, int alignParent) { 70 | return createRelative(width, height, leftMargin, topMargin, rightMargin, bottomMargin, alignParent, -1, -1); 71 | } 72 | 73 | public static RelativeLayout.LayoutParams createRelative(float width, float height, int leftMargin, int topMargin, int rightMargin, int bottomMargin, int alignRelative, int anchorRelative) { 74 | return createRelative(width, height, leftMargin, topMargin, rightMargin, bottomMargin, -1, alignRelative, anchorRelative); 75 | } 76 | 77 | public static RelativeLayout.LayoutParams createRelative(int width, int height, int alignParent, int alignRelative, int anchorRelative) { 78 | return createRelative(width, height, 0, 0, 0, 0, alignParent, alignRelative, anchorRelative); 79 | } 80 | 81 | public static RelativeLayout.LayoutParams createRelative(int width, int height) { 82 | return createRelative(width, height, 0, 0, 0, 0, -1, -1, -1); 83 | } 84 | 85 | public static RelativeLayout.LayoutParams createRelative(int width, int height, int alignParent) { 86 | return createRelative(width, height, 0, 0, 0, 0, alignParent, -1, -1); 87 | } 88 | 89 | public static RelativeLayout.LayoutParams createRelative(int width, int height, int alignRelative, int anchorRelative) { 90 | return createRelative(width, height, 0, 0, 0, 0, -1, alignRelative, anchorRelative); 91 | } 92 | 93 | public static LinearLayout.LayoutParams createLinear(int width, int height, float weight, int gravity, int leftMargin, int topMargin, int rightMargin, int bottomMargin) { 94 | LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(getSize(width), getSize(height), weight); 95 | layoutParams.setMargins(AndroidUtilities.dp(leftMargin), AndroidUtilities.dp(topMargin), AndroidUtilities.dp(rightMargin), AndroidUtilities.dp(bottomMargin)); 96 | layoutParams.gravity = gravity; 97 | return layoutParams; 98 | } 99 | 100 | public static LinearLayout.LayoutParams createLinear(int width, int height, float weight, int leftMargin, int topMargin, int rightMargin, int bottomMargin) { 101 | LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(getSize(width), getSize(height), weight); 102 | layoutParams.setMargins(AndroidUtilities.dp(leftMargin), AndroidUtilities.dp(topMargin), AndroidUtilities.dp(rightMargin), AndroidUtilities.dp(bottomMargin)); 103 | return layoutParams; 104 | } 105 | 106 | public static LinearLayout.LayoutParams createLinear(int width, int height, int gravity, int leftMargin, int topMargin, int rightMargin, int bottomMargin) { 107 | LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(getSize(width), getSize(height)); 108 | layoutParams.setMargins(AndroidUtilities.dp(leftMargin), AndroidUtilities.dp(topMargin), AndroidUtilities.dp(rightMargin), AndroidUtilities.dp(bottomMargin)); 109 | layoutParams.gravity = gravity; 110 | return layoutParams; 111 | } 112 | 113 | public static LinearLayout.LayoutParams createLinear(int width, int height, float leftMargin, float topMargin, float rightMargin, float bottomMargin) { 114 | LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(getSize(width), getSize(height)); 115 | layoutParams.setMargins(AndroidUtilities.dp(leftMargin), AndroidUtilities.dp(topMargin), AndroidUtilities.dp(rightMargin), AndroidUtilities.dp(bottomMargin)); 116 | return layoutParams; 117 | } 118 | 119 | public static LinearLayout.LayoutParams createLinear(int width, int height, float weight, int gravity) { 120 | LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(getSize(width), getSize(height), weight); 121 | layoutParams.gravity = gravity; 122 | return layoutParams; 123 | } 124 | 125 | public static LinearLayout.LayoutParams createLinear(int width, int height, int gravity) { 126 | LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(getSize(width), getSize(height)); 127 | layoutParams.gravity = gravity; 128 | return layoutParams; 129 | } 130 | 131 | public static LinearLayout.LayoutParams createLinear(int width, int height, float weight) { 132 | return new LinearLayout.LayoutParams(getSize(width), getSize(height), weight); 133 | } 134 | 135 | public static LinearLayout.LayoutParams createLinear(int width, int height) { 136 | return new LinearLayout.LayoutParams(getSize(width), getSize(height)); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/views/components/LineProgressView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This is the source code of Telegram for Android v. 2.0.x. 3 | * It is licensed under GNU GPL v. 2 or later. 4 | * You should have received a copy of the license in this archive (see LICENSE). 5 | * 6 | * Copyright Nikolai Kudashov, 2013-2018. 7 | */ 8 | 9 | package com.example.photopaint.views.components; 10 | 11 | import android.content.Context; 12 | import android.graphics.Canvas; 13 | import android.graphics.Paint; 14 | import android.view.View; 15 | import android.view.animation.DecelerateInterpolator; 16 | import com.example.photopaint.helpers.AndroidUtilities; 17 | 18 | public class LineProgressView extends View { 19 | 20 | private long lastUpdateTime; 21 | private float currentProgress; 22 | private float animationProgressStart; 23 | private long currentProgressTime; 24 | private float animatedProgressValue; 25 | private float animatedAlphaValue = 1.0f; 26 | 27 | private int backColor; 28 | private int progressColor; 29 | 30 | private static DecelerateInterpolator decelerateInterpolator; 31 | private static Paint progressPaint; 32 | 33 | public LineProgressView(Context context) { 34 | super(context); 35 | 36 | if (decelerateInterpolator == null) { 37 | decelerateInterpolator = new DecelerateInterpolator(); 38 | progressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 39 | progressPaint.setStrokeCap(Paint.Cap.ROUND); 40 | progressPaint.setStrokeWidth(AndroidUtilities.dp(2)); 41 | } 42 | } 43 | 44 | private void updateAnimation() { 45 | long newTime = System.currentTimeMillis(); 46 | long dt = newTime - lastUpdateTime; 47 | lastUpdateTime = newTime; 48 | 49 | if (animatedProgressValue != 1 && animatedProgressValue != currentProgress) { 50 | float progressDiff = currentProgress - animationProgressStart; 51 | if (progressDiff > 0) { 52 | currentProgressTime += dt; 53 | if (currentProgressTime >= 300) { 54 | animatedProgressValue = currentProgress; 55 | animationProgressStart = currentProgress; 56 | currentProgressTime = 0; 57 | } else { 58 | animatedProgressValue = animationProgressStart + progressDiff * decelerateInterpolator.getInterpolation(currentProgressTime / 300.0f); 59 | } 60 | } 61 | invalidate(); 62 | } 63 | if (animatedProgressValue >= 1 && animatedProgressValue == 1 && animatedAlphaValue != 0) { 64 | animatedAlphaValue -= dt / 200.0f; 65 | if (animatedAlphaValue <= 0) { 66 | animatedAlphaValue = 0.0f; 67 | } 68 | invalidate(); 69 | } 70 | } 71 | 72 | public void setProgressColor(int color) { 73 | progressColor = color; 74 | } 75 | 76 | public void setBackColor(int color) { 77 | backColor = color; 78 | } 79 | 80 | public void setProgress(float value, boolean animated) { 81 | if (!animated) { 82 | animatedProgressValue = value; 83 | animationProgressStart = value; 84 | } else { 85 | animationProgressStart = animatedProgressValue; 86 | } 87 | if (value != 1) { 88 | animatedAlphaValue = 1; 89 | } 90 | currentProgress = value; 91 | currentProgressTime = 0; 92 | 93 | lastUpdateTime = System.currentTimeMillis(); 94 | invalidate(); 95 | } 96 | 97 | public float getCurrentProgress() { 98 | return currentProgress; 99 | } 100 | 101 | public void onDraw(Canvas canvas) { 102 | if (backColor != 0 && animatedProgressValue != 1) { 103 | progressPaint.setColor(backColor); 104 | progressPaint.setAlpha((int) (255 * animatedAlphaValue)); 105 | int start = (int) (getWidth() * animatedProgressValue); 106 | canvas.drawRect(start, 0, getWidth(), getHeight(), progressPaint); 107 | } 108 | 109 | progressPaint.setColor(progressColor); 110 | progressPaint.setAlpha((int)(255 * animatedAlphaValue)); 111 | canvas.drawRect(0, 0, getWidth() * animatedProgressValue, getHeight(), progressPaint); 112 | updateAnimation(); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/views/components/Point.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.views.components; 2 | 3 | public class Point { 4 | public float x; 5 | public float y; 6 | 7 | public Point() { 8 | 9 | } 10 | 11 | public Point(float x, float y) { 12 | this.x = x; 13 | this.y = y; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/views/components/RadialProgressView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This is the source code of Telegram for Android v. 5.x.x. 3 | * It is licensed under GNU GPL v. 2 or later. 4 | * You should have received a copy of the license in this archive (see LICENSE). 5 | * 6 | * Copyright Nikolai Kudashov, 2013-2018. 7 | */ 8 | 9 | package com.example.photopaint.views.components; 10 | 11 | import android.content.Context; 12 | import android.graphics.Canvas; 13 | import android.graphics.Paint; 14 | import android.graphics.RectF; 15 | import android.graphics.drawable.Drawable; 16 | import android.view.View; 17 | import android.view.animation.AccelerateInterpolator; 18 | import android.view.animation.DecelerateInterpolator; 19 | import androidx.annotation.Keep; 20 | import com.example.photopaint.helpers.AndroidUtilities; 21 | import com.example.photopaint.views.actionbar.Theme; 22 | 23 | public class RadialProgressView extends View { 24 | 25 | private long lastUpdateTime; 26 | private float radOffset; 27 | private float currentCircleLength; 28 | private boolean risingCircleLength; 29 | private float currentProgressTime; 30 | private RectF cicleRect = new RectF(); 31 | private boolean useSelfAlpha; 32 | 33 | private int progressColor; 34 | 35 | private DecelerateInterpolator decelerateInterpolator; 36 | private AccelerateInterpolator accelerateInterpolator; 37 | private Paint progressPaint; 38 | private static final float rotationTime = 2000; 39 | private static final float risingTime = 500; 40 | private int size; 41 | 42 | public RadialProgressView(Context context) { 43 | super(context); 44 | 45 | size = AndroidUtilities.dp(40); 46 | 47 | progressColor = Theme.getColor(Theme.key_progressCircle); 48 | decelerateInterpolator = new DecelerateInterpolator(); 49 | accelerateInterpolator = new AccelerateInterpolator(); 50 | progressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 51 | progressPaint.setStyle(Paint.Style.STROKE); 52 | progressPaint.setStrokeCap(Paint.Cap.ROUND); 53 | progressPaint.setStrokeWidth(AndroidUtilities.dp(3)); 54 | progressPaint.setColor(progressColor); 55 | } 56 | 57 | public void setUseSelfAlpha(boolean value) { 58 | useSelfAlpha = value; 59 | } 60 | 61 | @Keep 62 | @Override 63 | public void setAlpha(float alpha) { 64 | super.setAlpha(alpha); 65 | if (useSelfAlpha) { 66 | Drawable background = getBackground(); 67 | int a = (int) (alpha * 255); 68 | if (background != null) { 69 | background.setAlpha(a); 70 | } 71 | progressPaint.setAlpha(a); 72 | } 73 | } 74 | 75 | private void updateAnimation() { 76 | long newTime = System.currentTimeMillis(); 77 | long dt = newTime - lastUpdateTime; 78 | if (dt > 17) { 79 | dt = 17; 80 | } 81 | lastUpdateTime = newTime; 82 | 83 | radOffset += 360 * dt / rotationTime; 84 | int count = (int) (radOffset / 360); 85 | radOffset -= count * 360; 86 | 87 | currentProgressTime += dt; 88 | if (currentProgressTime >= risingTime) { 89 | currentProgressTime = risingTime; 90 | } 91 | if (risingCircleLength) { 92 | currentCircleLength = 4 + 266 * accelerateInterpolator.getInterpolation(currentProgressTime / risingTime); 93 | } else { 94 | currentCircleLength = 4 - 270 * (1.0f - decelerateInterpolator.getInterpolation(currentProgressTime / risingTime)); 95 | } 96 | if (currentProgressTime == risingTime) { 97 | if (risingCircleLength) { 98 | radOffset += 270; 99 | currentCircleLength = -266; 100 | } 101 | risingCircleLength = !risingCircleLength; 102 | currentProgressTime = 0; 103 | } 104 | invalidate(); 105 | } 106 | 107 | public void setSize(int value) { 108 | size = value; 109 | invalidate(); 110 | } 111 | 112 | public void setStrokeWidth(float value) { 113 | progressPaint.setStrokeWidth(AndroidUtilities.dp(value)); 114 | } 115 | 116 | public void setProgressColor(int color) { 117 | progressColor = color; 118 | progressPaint.setColor(progressColor); 119 | } 120 | 121 | @Override 122 | protected void onDraw(Canvas canvas) { 123 | int x = (getMeasuredWidth() - size) / 2; 124 | int y = (getMeasuredHeight() - size) / 2; 125 | cicleRect.set(x, y, x + size, y + size); 126 | canvas.drawArc(cicleRect, radOffset, currentCircleLength, false, progressPaint); 127 | updateAnimation(); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/views/components/Rect.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.views.components; 2 | 3 | public class Rect { 4 | 5 | public float x; 6 | public float y; 7 | public float width; 8 | public float height; 9 | 10 | public Rect() { 11 | 12 | } 13 | 14 | public Rect(float x, float y, float width, float height) { 15 | this.x = x; 16 | this.y = y; 17 | this.width = width; 18 | this.height = height; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/views/components/Size.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.views.components; 2 | 3 | public class Size { 4 | public float width; 5 | public float height; 6 | 7 | public Size() { 8 | 9 | } 10 | 11 | public Size(float width, float height) { 12 | this.width = width; 13 | this.height = height; 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/views/components/TypefaceSpan.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.views.components; 2 | 3 | import android.graphics.Paint; 4 | import android.graphics.Typeface; 5 | import android.text.TextPaint; 6 | import android.text.style.MetricAffectingSpan; 7 | 8 | import com.example.photopaint.helpers.AndroidUtilities; 9 | 10 | public class TypefaceSpan extends MetricAffectingSpan { 11 | 12 | private Typeface typeface; 13 | private int textSize; 14 | private int color; 15 | 16 | public TypefaceSpan(Typeface tf) { 17 | typeface = tf; 18 | } 19 | 20 | public TypefaceSpan(Typeface tf, int size) { 21 | typeface = tf; 22 | textSize = size; 23 | } 24 | 25 | public TypefaceSpan(Typeface tf, int size, int textColor) { 26 | typeface = tf; 27 | if (size > 0) { 28 | textSize = size; 29 | } 30 | color = textColor; 31 | } 32 | 33 | public Typeface getTypeface() { 34 | return typeface; 35 | } 36 | 37 | public void setColor(int value) { 38 | color = value; 39 | } 40 | 41 | public boolean isMono() { 42 | return typeface == Typeface.MONOSPACE; 43 | } 44 | 45 | public boolean isBold() { 46 | return typeface == AndroidUtilities.getTypeface("fonts/rmedium.ttf"); 47 | } 48 | 49 | public boolean isItalic() { 50 | return typeface == AndroidUtilities.getTypeface("fonts/ritalic.ttf"); 51 | } 52 | 53 | @Override 54 | public void updateMeasureState(TextPaint p) { 55 | if (typeface != null) { 56 | p.setTypeface(typeface); 57 | } 58 | if (textSize != 0) { 59 | p.setTextSize(textSize); 60 | } 61 | p.setFlags(p.getFlags() | Paint.SUBPIXEL_TEXT_FLAG); 62 | } 63 | 64 | @Override 65 | public void updateDrawState(TextPaint tp) { 66 | if (typeface != null) { 67 | tp.setTypeface(typeface); 68 | } 69 | if (textSize != 0) { 70 | tp.setTextSize(textSize); 71 | } 72 | if (color != 0) { 73 | tp.setColor(color); 74 | } 75 | tp.setFlags(tp.getFlags() | Paint.SUBPIXEL_TEXT_FLAG); 76 | } 77 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/views/components/paint/Brush.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.views.components.paint; 2 | 3 | import android.graphics.*; 4 | import com.example.photopaint.helpers.ApplicationLoader; 5 | import com.example.photopaint.R; 6 | 7 | public interface Brush { 8 | 9 | float getSpacing(); 10 | float getAlpha(); 11 | float getAngle(); 12 | float getScale(); 13 | boolean isLightSaber(); 14 | boolean isMosaic(); 15 | Bitmap getStamp(); 16 | 17 | class Radial implements Brush { 18 | 19 | @Override 20 | public float getSpacing() { 21 | return 0.15f; 22 | } 23 | 24 | @Override 25 | public float getAlpha() { 26 | return 0.85f; 27 | } 28 | 29 | @Override 30 | public float getAngle() { 31 | return 0.0f; 32 | } 33 | 34 | @Override 35 | public float getScale() { 36 | return 1.0f; 37 | } 38 | 39 | @Override 40 | public boolean isLightSaber() { 41 | return false; 42 | } 43 | 44 | @Override 45 | public boolean isMosaic() { 46 | return false; 47 | } 48 | 49 | @Override 50 | public Bitmap getStamp() { 51 | BitmapFactory.Options options = new BitmapFactory.Options(); 52 | options.inScaled = false; 53 | return BitmapFactory.decodeResource(ApplicationLoader.applicationContext.getResources(), R.drawable.paint_radial_brush, options); 54 | } 55 | } 56 | 57 | class Elliptical implements Brush { 58 | 59 | @Override 60 | public float getSpacing() { 61 | return 0.04f; 62 | } 63 | 64 | @Override 65 | public float getAlpha() { 66 | return 0.3f; 67 | } 68 | 69 | @Override 70 | public float getAngle() { 71 | return (float) Math.toRadians(125.0); 72 | } 73 | 74 | @Override 75 | public float getScale() { 76 | return 1.5f; 77 | } 78 | 79 | @Override 80 | public boolean isLightSaber() { 81 | return false; 82 | } 83 | 84 | @Override 85 | public boolean isMosaic() { 86 | return false; 87 | } 88 | 89 | @Override 90 | public Bitmap getStamp() { 91 | BitmapFactory.Options options = new BitmapFactory.Options(); 92 | options.inScaled = false; 93 | return BitmapFactory.decodeResource(ApplicationLoader.applicationContext.getResources(), R.drawable.paint_elliptical_brush, options); 94 | } 95 | } 96 | 97 | class Neon implements Brush { 98 | 99 | @Override 100 | public float getSpacing() { 101 | return 0.07f; 102 | } 103 | 104 | @Override 105 | public float getAlpha() { 106 | return 0.7f; 107 | } 108 | 109 | @Override 110 | public float getAngle() { 111 | return 0.0f; 112 | } 113 | 114 | @Override 115 | public float getScale() { 116 | return 1.45f; 117 | } 118 | 119 | @Override 120 | public boolean isLightSaber() { 121 | return true; 122 | } 123 | 124 | @Override 125 | public boolean isMosaic() { 126 | return false; 127 | } 128 | 129 | @Override 130 | public Bitmap getStamp() { 131 | BitmapFactory.Options options = new BitmapFactory.Options(); 132 | options.inScaled = false; 133 | return BitmapFactory.decodeResource(ApplicationLoader.applicationContext.getResources(), R.drawable.paint_neon_brush, options); 134 | } 135 | } 136 | 137 | class Mosaic implements Brush { 138 | 139 | @Override 140 | public float getSpacing() { 141 | return 0.15f; 142 | } 143 | 144 | @Override 145 | public float getAlpha() { 146 | return 0.85f; 147 | } 148 | 149 | @Override 150 | public float getAngle() { 151 | return 0.0f; 152 | } 153 | 154 | @Override 155 | public float getScale() { 156 | return 3.0f; 157 | } 158 | 159 | @Override 160 | public boolean isLightSaber() { 161 | return false; 162 | } 163 | 164 | @Override 165 | public boolean isMosaic() { 166 | return true; 167 | } 168 | 169 | @Override 170 | public Bitmap getStamp() { 171 | BitmapFactory.Options options = new BitmapFactory.Options(); 172 | options.inScaled = false; 173 | return BitmapFactory.decodeResource(ApplicationLoader.applicationContext.getResources(), R.drawable.mosaic_white, options); 174 | } 175 | } 176 | 177 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/views/components/paint/GLMatrix.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.views.components.paint; 2 | 3 | import android.graphics.Matrix; 4 | 5 | public class GLMatrix { 6 | 7 | public static float[] LoadOrtho(float left, float right, float bottom, float top, float near, float far) { 8 | float r_l = right - left; 9 | float t_b = top - bottom; 10 | float f_n = far - near; 11 | float tx = -(right + left) / (right - left); 12 | float ty = -(top + bottom) / (top - bottom); 13 | float tz = -(far + near) / (far - near); 14 | 15 | float out[] = new float[16]; 16 | 17 | out[0] = 2.0f / r_l; 18 | out[1] = 0.0f; 19 | out[2] = 0.0f; 20 | out[3] = 0.0f; 21 | 22 | out[4] = 0.0f; 23 | out[5] = 2.0f / t_b; 24 | out[6] = 0.0f; 25 | out[7] = 0.0f; 26 | 27 | out[8] = 0.0f; 28 | out[9] = 0.0f; 29 | out[10] = -2.0f / f_n; 30 | out[11] = 0.0f; 31 | 32 | out[12] = tx; 33 | out[13] = ty; 34 | out[14] = tz; 35 | out[15] = 1.0f; 36 | 37 | return out; 38 | } 39 | 40 | public static float[] LoadGraphicsMatrix(Matrix matrix) { 41 | float m[] = new float[16]; 42 | float v[] = new float[9]; 43 | matrix.getValues(v); 44 | 45 | m[0] = v[Matrix.MSCALE_X]; //m.a; 46 | m[1] = v[Matrix.MSKEW_X]; //m.b; 47 | m[2] = 0.0f; 48 | m[3] = 0.0f; 49 | 50 | m[4] = v[Matrix.MSKEW_Y]; //m.c; 51 | m[5] = v[Matrix.MSCALE_Y]; //m.d; 52 | m[6] = 0.0f; 53 | m[7] = 0.0f; 54 | 55 | m[8] = 0.0f; 56 | m[9] = 0.0f; 57 | m[10] = 1.0f; 58 | m[11] = 0.0f; 59 | 60 | m[12] = v[Matrix.MTRANS_X]; //m.tx; 61 | m[13] = v[Matrix.MTRANS_Y]; //m.ty; 62 | m[14] = 0.0f; 63 | m[15] = 1.0f; 64 | 65 | return m; 66 | } 67 | 68 | public static float[] MultiplyMat4f(float[] a, float[] b) { 69 | float out[] = new float[16]; 70 | 71 | out[0] = a[0] * b[0] + a[4] * b[1] + a[8] * b[2] + a[12] * b[3]; 72 | out[1] = a[1] * b[0] + a[5] * b[1] + a[9] * b[2] + a[13] * b[3]; 73 | out[2] = a[2] * b[0] + a[6] * b[1] + a[10] * b[2] + a[14] * b[3]; 74 | out[3] = a[3] * b[0] + a[7] * b[1] + a[11] * b[2] + a[15] * b[3]; 75 | 76 | out[4] = a[0] * b[4] + a[4] * b[5] + a[8] * b[6] + a[12] * b[7]; 77 | out[5] = a[1] * b[4] + a[5] * b[5] + a[9] * b[6] + a[13] * b[7]; 78 | out[6] = a[2] * b[4] + a[6] * b[5] + a[10] * b[6] + a[14] * b[7]; 79 | out[7] = a[3] * b[4] + a[7] * b[5] + a[11] * b[6] + a[15] * b[7]; 80 | 81 | out[8] = a[0] * b[8] + a[4] * b[9] + a[8] * b[10] + a[12] * b[11]; 82 | out[9] = a[1] * b[8] + a[5] * b[9] + a[9] * b[10] + a[13] * b[11]; 83 | out[10] = a[2] * b[8] + a[6] * b[9] + a[10] * b[10] + a[14] * b[11]; 84 | out[11] = a[3] * b[8] + a[7] * b[9] + a[11] * b[10] + a[15] * b[11]; 85 | 86 | out[12] = a[0] * b[12] + a[4] * b[13] + a[8] * b[14] + a[12] * b[15]; 87 | out[13] = a[1] * b[12] + a[5] * b[13] + a[9] * b[14] + a[13] * b[15]; 88 | out[14] = a[2] * b[12] + a[6] * b[13] + a[10] * b[14] + a[14] * b[15]; 89 | out[15] = a[3] * b[12] + a[7] * b[13] + a[11] * b[14] + a[15] * b[15]; 90 | 91 | return out; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/views/components/paint/Input.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.views.components.paint; 2 | 3 | import android.graphics.Matrix; 4 | import android.view.MotionEvent; 5 | import com.example.photopaint.helpers.AndroidUtilities; 6 | 7 | import java.util.Vector; 8 | 9 | public class Input { 10 | 11 | private RenderView renderView; 12 | 13 | private boolean beganDrawing; 14 | private boolean isFirst; 15 | private boolean hasMoved; 16 | private boolean clearBuffer; 17 | 18 | private Point lastLocation; 19 | private double lastRemainder; 20 | 21 | private Point[] points = new Point[3]; 22 | private int pointsCount; 23 | 24 | private Matrix invertMatrix; 25 | private float[] tempPoint = new float[2]; 26 | 27 | public Input(RenderView render) { 28 | renderView = render; 29 | } 30 | 31 | public void setMatrix(Matrix m) { 32 | invertMatrix = new Matrix(); 33 | m.invert(invertMatrix); 34 | } 35 | 36 | public void process(MotionEvent event) { 37 | int action = event.getActionMasked(); 38 | float x = event.getX(); 39 | float y = renderView.getHeight() - event.getY(); 40 | 41 | tempPoint[0] = x; 42 | tempPoint[1] = y; 43 | invertMatrix.mapPoints(tempPoint); 44 | 45 | Point location = new Point(tempPoint[0], tempPoint[1], 1.0f); 46 | 47 | switch (action) { 48 | case MotionEvent.ACTION_DOWN: 49 | case MotionEvent.ACTION_MOVE: { 50 | if (!beganDrawing) { 51 | // 如果当前的状态不是正在绘制中,那么开始绘制 52 | beganDrawing = true; 53 | hasMoved = false; 54 | isFirst = true; 55 | 56 | // 记录上一个点 57 | lastLocation = location; 58 | 59 | points[0] = location; 60 | pointsCount = 1; 61 | 62 | clearBuffer = true; 63 | } else { 64 | //每次移动都是直线距离(无数个短的直线形成曲线),这里的distance是三维直线距离 65 | float distance = location.getDistanceTo(lastLocation); 66 | if (distance < AndroidUtilities.dp(5.0f)) { 67 | // 移动距离小于5dp时,绘制状态不做变更 68 | return; 69 | } 70 | 71 | if (!hasMoved) { 72 | // 当发生移动之后回调onBeganDrawing 73 | renderView.onBeganDrawing(); 74 | hasMoved = true; 75 | } 76 | 77 | // 把当前点记录下来 78 | points[pointsCount] = location; 79 | pointsCount++; 80 | 81 | if (pointsCount == 3) { 82 | //中间添加过渡的点让路径path更顺滑,并绘制这些点 83 | smoothenAndPaintPoints(false); 84 | } 85 | 86 | lastLocation = location; 87 | } 88 | } 89 | break; 90 | 91 | case MotionEvent.ACTION_UP: { 92 | if (!hasMoved) { 93 | if (renderView.shouldDraw()) { 94 | location.edge = true; 95 | paintPath(new Path(location)); 96 | } 97 | reset(); 98 | } else if (pointsCount > 0) { 99 | smoothenAndPaintPoints(true); 100 | } 101 | 102 | pointsCount = 0; 103 | 104 | renderView.getPainting().commitStroke(renderView.getCurrentColor()); 105 | beganDrawing = false; 106 | 107 | renderView.onFinishedDrawing(hasMoved); 108 | } 109 | break; 110 | } 111 | } 112 | 113 | private void reset() { 114 | pointsCount = 0; 115 | } 116 | 117 | private void smoothenAndPaintPoints(boolean ended) { 118 | if (pointsCount > 2) { 119 | Vector points = new Vector<>(); 120 | 121 | Point prev2 = this.points[0]; 122 | Point prev1 = this.points[1]; 123 | Point cur = this.points[2]; 124 | 125 | if (cur == null || prev1 == null || prev2 == null) { 126 | return; 127 | } 128 | 129 | Point midPoint1 = prev1.multiplySum(prev2, 0.5f);// 计算缓存的前两个点的中间点 130 | midPoint1.mosaicColor = renderView.getMosaicColor((float) midPoint1.x, (float) midPoint1.y); 131 | Point midPoint2 = cur.multiplySum(prev1, 0.5f);// 计算当前点和上一个点的中间点 132 | midPoint2.mosaicColor = renderView.getMosaicColor((float) midPoint2.x, (float) midPoint2.y); 133 | 134 | int segmentDistance = renderView.isMosaic() ? 30 : 1;//设置线段的距离为1px. 135 | float distance = midPoint1.getDistanceTo(midPoint2);// 计算两个中间点的距离 136 | int numberOfSegments = (int) Math.min(48, Math.max(Math.floor(distance / segmentDistance), 24));// 计算可以分成多少段 137 | 138 | float t = 0.0f; 139 | float step = 1.0f / (float) numberOfSegments; 140 | 141 | for (int j = 0; j < numberOfSegments; j++) { 142 | Point point = smoothPoint(midPoint1, midPoint2, prev1, t);// 添加过渡点 143 | point.mosaicColor = renderView.getMosaicColor((float) point.x, (float) point.y); 144 | if (isFirst) {// 是否为起始点,如果是起始点和结束点都需要标记 145 | point.edge = true; 146 | isFirst = false; 147 | } 148 | points.add(point); 149 | t += step; 150 | } 151 | 152 | if (ended) {// 是否为结束点,当手势抬起的时候标记为结束点 153 | midPoint2.edge = true; 154 | } 155 | points.add(midPoint2); 156 | 157 | Point[] result = new Point[points.size()]; 158 | points.toArray(result); 159 | 160 | Path path = new Path(result); 161 | paintPath(path); 162 | 163 | System.arraycopy(this.points, 1, this.points, 0, 2); 164 | 165 | if (ended) { 166 | pointsCount = 0; 167 | } else { 168 | pointsCount = 2; 169 | } 170 | } 171 | else { 172 | Point[] result = new Point[pointsCount]; 173 | System.arraycopy(this.points, 0, result, 0, pointsCount); 174 | Path path = new Path(result); 175 | paintPath(path); 176 | } 177 | } 178 | 179 | private Point smoothPoint(Point midPoint1, Point midPoint2, Point prev1, float t) { 180 | double a1 = Math.pow(1.0f - t, 2); 181 | double a2 = (2.0f * (1.0f - t) * t); 182 | double a3 = t * t; 183 | 184 | return new Point(midPoint1.x * a1 + prev1.x * a2 + midPoint2.x * a3, midPoint1.y * a1 + prev1.y * a2 + midPoint2.y * a3, 1.0f); 185 | } 186 | 187 | private void paintPath(final Path path) { 188 | path.setup(renderView.getCurrentColor(), renderView.getCurrentWeight(), renderView.getCurrentBrush()); 189 | 190 | if (clearBuffer) { 191 | lastRemainder = 0.0f; 192 | } 193 | 194 | path.remainder = lastRemainder;// 接续上个path 195 | 196 | renderView.getPainting().paintStroke(path, clearBuffer, new Runnable() { 197 | @Override 198 | public void run() { 199 | AndroidUtilities.runOnUIThread(new Runnable() { 200 | @Override 201 | public void run() { 202 | lastRemainder = path.remainder; 203 | clearBuffer = false; 204 | } 205 | }); 206 | } 207 | }); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/views/components/paint/Path.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.views.components.paint; 2 | 3 | import java.util.Arrays; 4 | import java.util.Vector; 5 | 6 | public class Path { 7 | 8 | public double remainder; 9 | private Vector points = new Vector<>(); 10 | private int color; 11 | private float baseWeight; 12 | private Brush brush; 13 | 14 | public Path(Point point) { 15 | points.add(point); 16 | } 17 | 18 | public Path(Point[] points) { 19 | this.points.addAll(Arrays.asList(points)); 20 | } 21 | 22 | public int getLength() { 23 | if (points == null) { 24 | return 0; 25 | } 26 | return points.size(); 27 | } 28 | 29 | public Point[] getPoints() { 30 | Point[] points = new Point[this.points.size()]; 31 | this.points.toArray(points); 32 | return points; 33 | } 34 | 35 | public int getColor() { 36 | return color; 37 | } 38 | 39 | public float getBaseWeight() { 40 | return baseWeight; 41 | } 42 | 43 | public Brush getBrush() { 44 | return brush; 45 | } 46 | 47 | public void setup(int color, float baseWeight, Brush brush) { 48 | this.color = color; 49 | this.baseWeight = baseWeight; 50 | this.brush = brush; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/views/components/paint/PhotoFace.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.views.components.paint; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.PointF; 5 | import com.google.android.gms.vision.face.Face; 6 | import com.google.android.gms.vision.face.Landmark; 7 | import com.example.photopaint.views.components.Size; 8 | import com.example.photopaint.views.components.Point; 9 | 10 | import java.util.List; 11 | 12 | public class PhotoFace { 13 | 14 | private float width; 15 | private float angle; 16 | 17 | private Point foreheadPoint; 18 | 19 | private Point eyesCenterPoint; 20 | private float eyesDistance; 21 | 22 | private Point mouthPoint; 23 | private Point chinPoint; 24 | 25 | public PhotoFace(Face face, Bitmap sourceBitmap, Size targetSize, boolean sideward) { 26 | List landmarks = face.getLandmarks(); 27 | 28 | Point leftEyePoint = null; 29 | Point rightEyePoint = null; 30 | 31 | Point leftMouthPoint = null; 32 | Point rightMouthPoint = null; 33 | 34 | for (Landmark landmark : landmarks) { 35 | PointF point = landmark.getPosition(); 36 | 37 | switch (landmark.getType()) { 38 | case Landmark.LEFT_EYE: { 39 | leftEyePoint = transposePoint(point, sourceBitmap, targetSize, sideward); 40 | } 41 | break; 42 | 43 | case Landmark.RIGHT_EYE: { 44 | rightEyePoint = transposePoint(point, sourceBitmap, targetSize, sideward); 45 | } 46 | break; 47 | 48 | case Landmark.LEFT_MOUTH: { 49 | leftMouthPoint = transposePoint(point, sourceBitmap, targetSize, sideward); 50 | } 51 | break; 52 | 53 | case Landmark.RIGHT_MOUTH: { 54 | rightMouthPoint = transposePoint(point, sourceBitmap, targetSize, sideward); 55 | } 56 | break; 57 | } 58 | } 59 | 60 | if (leftEyePoint != null && rightEyePoint != null) { 61 | eyesCenterPoint = new Point(0.5f * leftEyePoint.x + 0.5f * rightEyePoint.x, 62 | 0.5f * leftEyePoint.y + 0.5f * rightEyePoint.y); 63 | eyesDistance = (float)Math.hypot(rightEyePoint.x - leftEyePoint.x, rightEyePoint.y - leftEyePoint.y); 64 | angle = (float)Math.toDegrees(Math.PI + Math.atan2(rightEyePoint.y - leftEyePoint.y, rightEyePoint.x - leftEyePoint.x)); 65 | 66 | width = eyesDistance * 2.35f; 67 | 68 | float foreheadHeight = 0.8f * eyesDistance; 69 | float upAngle = (float)Math.toRadians(angle - 90); 70 | foreheadPoint = new Point(eyesCenterPoint.x + foreheadHeight * (float)Math.cos(upAngle), 71 | eyesCenterPoint.y + foreheadHeight * (float)Math.sin(upAngle)); 72 | } 73 | 74 | if (leftMouthPoint != null && rightMouthPoint != null) { 75 | mouthPoint = new Point(0.5f * leftMouthPoint.x + 0.5f * rightMouthPoint.x, 76 | 0.5f * leftMouthPoint.y + 0.5f * rightMouthPoint.y); 77 | 78 | float chinDepth = 0.7f * eyesDistance; 79 | float downAngle = (float)Math.toRadians(angle + 90); 80 | chinPoint = new Point(mouthPoint.x + chinDepth * (float)Math.cos(downAngle), 81 | mouthPoint.y + chinDepth * (float)Math.sin(downAngle)); 82 | } 83 | } 84 | 85 | public boolean isSufficient() { 86 | return eyesCenterPoint != null; 87 | } 88 | 89 | private Point transposePoint(PointF point, Bitmap sourceBitmap, Size targetSize, boolean sideward) { 90 | float bitmapW = sideward ? sourceBitmap.getHeight() : sourceBitmap.getWidth(); 91 | float bitmapH = sideward ? sourceBitmap.getWidth() : sourceBitmap.getHeight(); 92 | return new Point(targetSize.width * point.x / bitmapW, 93 | targetSize.height * point.y / bitmapH); 94 | } 95 | 96 | public Point getPointForAnchor(int anchor) { 97 | switch (anchor) { 98 | case 0: { 99 | return foreheadPoint; 100 | } 101 | 102 | case 1: { 103 | return eyesCenterPoint; 104 | } 105 | 106 | case 2: { 107 | return mouthPoint; 108 | } 109 | 110 | case 3: { 111 | return chinPoint; 112 | } 113 | 114 | default: { 115 | return null; 116 | } 117 | } 118 | } 119 | 120 | public float getWidthForAnchor(int anchor) { 121 | if (anchor == 1) 122 | return eyesDistance; 123 | 124 | return width; 125 | } 126 | 127 | public float getAngle() { 128 | return angle; 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/views/components/paint/Point.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.views.components.paint; 2 | 3 | import android.graphics.Color; 4 | import android.graphics.PointF; 5 | 6 | public class Point { 7 | 8 | public double x; 9 | public double y; 10 | public double z; 11 | 12 | public boolean edge; 13 | public int mosaicColor; 14 | 15 | public Point(double x, double y, double z) { 16 | this.x = x; 17 | this.y = y; 18 | this.z = z; 19 | } 20 | 21 | public Point(Point point) { 22 | x = point.x; 23 | y = point.y; 24 | z = point.z; 25 | } 26 | 27 | @Override 28 | public boolean equals(Object obj) { 29 | if (obj == null) { 30 | return false; 31 | } 32 | if (obj == this) { 33 | return true; 34 | } 35 | if (!(obj instanceof Point)) { 36 | return false; 37 | } 38 | Point other = (Point) obj; 39 | return this.x == other.x && this.y == other.y && this.z == other.z; 40 | } 41 | 42 | Point multiplySum(Point point, double scalar) { 43 | return new Point((x + point.x) * scalar, (y + point.y) * scalar, (z + point.z) * scalar); 44 | } 45 | 46 | Point multiplyAndAdd(double scalar, Point point) { 47 | return new Point((x * scalar) + point.x, (y * scalar) + point.y, (z * scalar) + point.z); 48 | } 49 | 50 | private int getSmoothColor(Point point, double scalar){ 51 | int r1 = Color.red(mosaicColor); 52 | int g1 = Color.green(mosaicColor); 53 | int b1 = Color.blue(mosaicColor); 54 | int a1 = Color.alpha(mosaicColor); 55 | 56 | int r2 = Color.red(point.getMosaicColor()); 57 | int g2 = Color.green(point.getMosaicColor()); 58 | int b2 = Color.blue(point.getMosaicColor()); 59 | int a2 = Color.alpha(point.getMosaicColor()); 60 | 61 | int r = (int) Math.min(255,(r1 + r2) * scalar); 62 | int g = (int) Math.min(255,(g1 + g2) * scalar); 63 | int b = (int) Math.min(255,(b1 + b2) * scalar); 64 | int a = (int) Math.min(255,(a1 + a2) * scalar); 65 | 66 | return Color.argb(a, r, g, b); 67 | } 68 | 69 | private int addColor(int color1, int color2){ 70 | int r1 = Color.red(color1); 71 | int g1 = Color.green(color1); 72 | int b1 = Color.blue(color1); 73 | int a1 = Color.alpha(color1); 74 | 75 | int r2 = Color.red(color2); 76 | int g2 = Color.green(color2); 77 | int b2 = Color.blue(color2); 78 | int a2 = Color.alpha(color2); 79 | 80 | int r = Math.min(255,(r1 + r2)); 81 | int g = Math.min(255,(g1 + g2)); 82 | int b = Math.min(255,(b1 + b2)); 83 | int a = Math.min(255,(a1 + a2)); 84 | 85 | return Color.argb(a, r, g, b); 86 | } 87 | 88 | private int substractColor(int color1, int color2){ 89 | int r1 = Color.red(color1); 90 | int g1 = Color.green(color1); 91 | int b1 = Color.blue(color1); 92 | int a1 = Color.alpha(color1); 93 | 94 | int r2 = Color.red(color2); 95 | int g2 = Color.green(color2); 96 | int b2 = Color.blue(color2); 97 | int a2 = Color.alpha(color2); 98 | 99 | int r = Math.max(0,(r1 - r2)); 100 | int g = Math.max(0,(g1 - g2)); 101 | int b = Math.max(0,(b1 - b2)); 102 | int a = Math.max(0,(a1 - a2)); 103 | 104 | return Color.argb(a, r, g, b); 105 | } 106 | 107 | void alteringAddMultiplication(Point point, double scalar) { 108 | x = x + (point.x * scalar); 109 | y = y + (point.y * scalar); 110 | z = z + (point.z * scalar); 111 | } 112 | 113 | Point add(Point point) { 114 | return new Point(x + point.x, y + point.y, z + point.z); 115 | } 116 | 117 | Point substract(Point point) { 118 | return new Point(x - point.x, y - point.y, z - point.z); 119 | } 120 | 121 | Point multiplyByScalar(double scalar) { 122 | return new Point(x * scalar, y * scalar, z * scalar); 123 | } 124 | 125 | Point getNormalized() { 126 | return multiplyByScalar(1.0 / getMagnitude()); 127 | } 128 | 129 | private double getMagnitude() { 130 | return Math.sqrt(x * x + y * y + z * z); 131 | } 132 | 133 | float getDistanceTo(Point point) { 134 | return (float) Math.sqrt(Math.pow(x - point.x, 2) + Math.pow(y - point.y, 2) + Math.pow(z - point.z, 2)); 135 | } 136 | 137 | PointF toPointF() { 138 | return new PointF((float) x, (float) y); 139 | } 140 | 141 | public int getMosaicColor(){ 142 | return this.mosaicColor; 143 | } 144 | } 145 | 146 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/views/components/paint/Render.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.views.components.paint; 2 | 3 | import android.graphics.Color; 4 | import android.graphics.Matrix; 5 | import android.graphics.PointF; 6 | import android.graphics.RectF; 7 | import android.opengl.GLES20; 8 | 9 | import java.nio.ByteBuffer; 10 | import java.nio.ByteOrder; 11 | import java.nio.FloatBuffer; 12 | 13 | public class Render { 14 | 15 | public static RectF RenderPath(Path path, RenderState state) { 16 | state.baseWeight = path.getBaseWeight(); 17 | state.spacing = path.getBrush().getSpacing(); 18 | state.alpha = path.getBrush().getAlpha(); 19 | state.angle = path.getBrush().getAngle(); 20 | state.scale = path.getBrush().getScale(); 21 | // 给马赛克画刷赋默认值 22 | state.red = 0.0f; 23 | state.green = 0.0f; 24 | state.blue = 0.0f; 25 | state.z = path.getBrush().isMosaic() ? -1f : 1f; 26 | 27 | int length = path.getLength(); 28 | if (length == 0) { 29 | return null; 30 | } 31 | 32 | if (length == 1) { 33 | // 如果是点就绘制Stamp 34 | if(path.getBrush().isMosaic()) { 35 | // 如果是马赛克就给画笔颜色重新赋值 36 | state.red = (float) (Color.red(path.getPoints()[0].getMosaicColor())) / 255; 37 | state.green = (float) (Color.green(path.getPoints()[0].getMosaicColor())) / 255; 38 | state.blue = (float) (Color.blue(path.getPoints()[0].getMosaicColor())) / 255; 39 | state.alpha = (float) (Color.alpha(path.getPoints()[0].getMosaicColor())) / 255; 40 | } 41 | 42 | PaintStamp(path.getPoints()[0], state); 43 | } else { 44 | // 如果是线就绘制点与点之间的线段 45 | Point[] points = path.getPoints(); 46 | state.prepare(); 47 | 48 | for (int i = 0; i < points.length - 1; i++) { 49 | if(path.getBrush().isMosaic()) { 50 | // 如果是马赛克就给画笔颜色重新赋值 51 | state.red = (float) (Color.red(path.getPoints()[i].getMosaicColor())) / 255; 52 | state.green = (float) (Color.green(path.getPoints()[i].getMosaicColor())) / 255; 53 | state.blue = (float) (Color.blue(path.getPoints()[i].getMosaicColor())) / 255; 54 | state.alpha = (float) (Color.alpha(path.getPoints()[1].getMosaicColor())) / 255; 55 | } 56 | PaintSegment(points[i], points[i + 1], state); 57 | } 58 | } 59 | 60 | path.remainder = state.remainder; 61 | 62 | return Draw(state); 63 | } 64 | 65 | private static void PaintSegment(Point lastPoint, Point point, RenderState state) { 66 | double distance = lastPoint.getDistanceTo(point); 67 | Point vector = point.substract(lastPoint); 68 | Point unitVector = new Point(1.0f, 1.0f, 0.0f); 69 | float vectorAngle = Math.abs(state.angle) > 0.0f ? state.angle : (float) Math.atan2(vector.y, vector.x); 70 | 71 | float brushWeight = state.baseWeight * state.scale; 72 | double step = Math.max(1.0f, state.spacing * brushWeight); 73 | 74 | if (distance > 0.0) { 75 | unitVector = vector.multiplyByScalar(1.0 / distance); 76 | } 77 | 78 | float boldenedAlpha = Math.min(1.0f, state.alpha * 1.15f); 79 | boolean boldenHead = lastPoint.edge; 80 | boolean boldenTail = point.edge; 81 | 82 | int count = (int) Math.ceil((distance - state.remainder) / step); 83 | int currentCount = state.getCount(); 84 | state.appendValuesCount(count); 85 | state.setPosition(currentCount); 86 | 87 | Point start = lastPoint.add(unitVector.multiplyByScalar(state.remainder)); 88 | 89 | boolean succeed = true; 90 | double f = state.remainder; 91 | for (; f <= distance; f += step) { 92 | float alpha = boldenHead ? boldenedAlpha : state.alpha; 93 | succeed = state.addPoint(start.toPointF(), brushWeight, vectorAngle, alpha, -1); 94 | if (!succeed) { 95 | break; 96 | } 97 | 98 | start = start.add(unitVector.multiplyByScalar(step)); 99 | boldenHead = false; 100 | } 101 | 102 | if (succeed && boldenTail) { 103 | state.appendValuesCount(1); 104 | state.addPoint(point.toPointF(), brushWeight, vectorAngle, boldenedAlpha, -1); 105 | } 106 | 107 | state.remainder = f - distance; 108 | } 109 | 110 | private static void PaintStamp(Point point, RenderState state) { 111 | float brushWeight = state.baseWeight * state.scale; 112 | PointF start = point.toPointF(); 113 | float angle = Math.abs(state.angle) > 0.0f ? state.angle : 0.0f; 114 | float alpha = state.alpha; 115 | 116 | state.prepare(); 117 | state.appendValuesCount(1); 118 | state.addPoint(start, brushWeight, angle, alpha, 0); 119 | } 120 | 121 | private static RectF Draw(RenderState state) { 122 | RectF dataBounds = new RectF(0, 0, 0, 0); 123 | 124 | int count = state.getCount(); 125 | if (count == 0) { 126 | return dataBounds; 127 | } 128 | 129 | int vertexDataSize = 9 * Float.SIZE / 8; 130 | int capacity = vertexDataSize * (count * 4 + (count - 1) * 2); 131 | ByteBuffer bb = ByteBuffer.allocateDirect(capacity); 132 | bb.order(ByteOrder.nativeOrder()); 133 | FloatBuffer vertexData = bb.asFloatBuffer(); 134 | vertexData.position(0); 135 | state.setPosition(0); 136 | 137 | int n = 0; 138 | for (int i = 0; i < count; i++) { 139 | float x = state.read(); 140 | float y = state.read(); 141 | float z = state.read(); 142 | float size = state.read(); 143 | float angle = state.read(); 144 | float alpha = state.read(); 145 | float red = state.read(); 146 | float green = state.read(); 147 | float blue = state.read(); 148 | 149 | RectF rect = new RectF(x - size, y - size, x + size, y + size); 150 | float[] points = new float[]{ 151 | rect.left, rect.top, 152 | rect.right, rect.top, 153 | rect.left, rect.bottom, 154 | rect.right, rect.bottom 155 | }; 156 | 157 | float centerX = rect.centerX(); 158 | float centerY = rect.centerY(); 159 | 160 | Matrix t = new Matrix(); 161 | t.setRotate((float) Math.toDegrees(angle), centerX, centerY); 162 | t.mapPoints(points); 163 | t.mapRect(rect); 164 | 165 | Utils.RectFIntegral(rect); 166 | dataBounds.union(rect); 167 | 168 | if (n != 0) { 169 | vertexData.put(points[0]); 170 | vertexData.put(points[1]); 171 | vertexData.put(z); 172 | vertexData.put(0); 173 | vertexData.put(0); 174 | vertexData.put(alpha); 175 | vertexData.put(red); 176 | vertexData.put(green); 177 | vertexData.put(blue); 178 | n++; 179 | } 180 | 181 | vertexData.put(points[0]); 182 | vertexData.put(points[1]); 183 | vertexData.put(z); 184 | vertexData.put(0); 185 | vertexData.put(0); 186 | vertexData.put(alpha); 187 | vertexData.put(red); 188 | vertexData.put(green); 189 | vertexData.put(blue); 190 | n++; 191 | 192 | vertexData.put(points[2]); 193 | vertexData.put(points[3]); 194 | vertexData.put(z); 195 | vertexData.put(1); 196 | vertexData.put(0); 197 | vertexData.put(alpha); 198 | vertexData.put(red); 199 | vertexData.put(green); 200 | vertexData.put(blue); 201 | n++; 202 | 203 | vertexData.put(points[4]); 204 | vertexData.put(points[5]); 205 | vertexData.put(z); 206 | vertexData.put(0); 207 | vertexData.put(1); 208 | vertexData.put(alpha); 209 | vertexData.put(red); 210 | vertexData.put(green); 211 | vertexData.put(blue); 212 | n++; 213 | 214 | vertexData.put(points[6]); 215 | vertexData.put(points[7]); 216 | vertexData.put(z); 217 | vertexData.put(1); 218 | vertexData.put(1); 219 | vertexData.put(alpha); 220 | vertexData.put(red); 221 | vertexData.put(green); 222 | vertexData.put(blue); 223 | n++; 224 | 225 | if (i != count - 1) { 226 | vertexData.put(points[6]); 227 | vertexData.put(points[7]); 228 | vertexData.put(z); 229 | vertexData.put(1); 230 | vertexData.put(1); 231 | vertexData.put(alpha); 232 | vertexData.put(red); 233 | vertexData.put(green); 234 | vertexData.put(blue); 235 | n++; 236 | } 237 | } 238 | 239 | vertexData.position(0); 240 | FloatBuffer coordData = vertexData.slice(); 241 | GLES20.glVertexAttribPointer(0, 3, GLES20.GL_FLOAT, false, vertexDataSize, coordData); 242 | GLES20.glEnableVertexAttribArray(0); 243 | 244 | vertexData.position(3); 245 | FloatBuffer texData = vertexData.slice(); 246 | GLES20.glVertexAttribPointer(1, 2, GLES20.GL_FLOAT, true, vertexDataSize, texData); 247 | GLES20.glEnableVertexAttribArray(1); 248 | 249 | vertexData.position(5); 250 | FloatBuffer alphaData = vertexData.slice(); 251 | GLES20.glVertexAttribPointer(2, 1, GLES20.GL_FLOAT, true, vertexDataSize, alphaData); 252 | GLES20.glEnableVertexAttribArray(2); 253 | 254 | vertexData.position(6); 255 | FloatBuffer redData = vertexData.slice(); 256 | GLES20.glVertexAttribPointer(3, 1, GLES20.GL_FLOAT, true, vertexDataSize, redData); 257 | GLES20.glEnableVertexAttribArray(3); 258 | 259 | vertexData.position(7); 260 | FloatBuffer greenData = vertexData.slice(); 261 | GLES20.glVertexAttribPointer(4, 1, GLES20.GL_FLOAT, true, vertexDataSize, greenData); 262 | GLES20.glEnableVertexAttribArray(4); 263 | 264 | vertexData.position(8); 265 | FloatBuffer blueData = vertexData.slice(); 266 | GLES20.glVertexAttribPointer(5, 1, GLES20.GL_FLOAT, true, vertexDataSize, blueData); 267 | GLES20.glEnableVertexAttribArray(5); 268 | 269 | 270 | GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, n); 271 | 272 | return dataBounds; 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/views/components/paint/RenderState.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.views.components.paint; 2 | 3 | import android.graphics.PointF; 4 | 5 | import java.nio.ByteBuffer; 6 | import java.nio.ByteOrder; 7 | 8 | public class RenderState { 9 | private static final int DEFAULT_STATE_SIZE = 256; 10 | 11 | public float baseWeight; 12 | public float spacing; 13 | public float alpha; 14 | public float angle; 15 | public float scale; 16 | public float red; 17 | public float green; 18 | public float blue; 19 | public float z; 20 | 21 | public double remainder; 22 | 23 | private int count; 24 | private int allocatedCount; 25 | private ByteBuffer buffer; 26 | 27 | public int getCount() { 28 | return count; 29 | } 30 | 31 | public void prepare() { 32 | count = 0; 33 | 34 | if (buffer != null) { 35 | return; 36 | } 37 | 38 | allocatedCount = DEFAULT_STATE_SIZE; 39 | buffer = ByteBuffer.allocateDirect(allocatedCount * 9 * 4); 40 | buffer.order(ByteOrder.nativeOrder()); 41 | buffer.position(0); 42 | } 43 | 44 | public float read() { 45 | return buffer.getFloat(); 46 | } 47 | 48 | public void setPosition(int position) { 49 | if (buffer == null || position < 0 || position >= allocatedCount) { 50 | return; 51 | } 52 | buffer.position(position * 9 * 4); 53 | } 54 | 55 | public void appendValuesCount(int count) { 56 | int newTotalCount = this.count + count; 57 | 58 | if (newTotalCount > allocatedCount || buffer == null) { 59 | resizeBuffer(); 60 | } 61 | 62 | this.count = newTotalCount; 63 | } 64 | 65 | public void resizeBuffer() { 66 | if (buffer != null) { 67 | buffer = null; 68 | } 69 | 70 | allocatedCount = Math.max(allocatedCount * 2, DEFAULT_STATE_SIZE); 71 | 72 | buffer = ByteBuffer.allocateDirect(allocatedCount * 9 * 4); 73 | buffer.order(ByteOrder.nativeOrder()); 74 | buffer.position(0); 75 | } 76 | 77 | public boolean addPoint(PointF point, float size, float angle, float alpha, int index) { 78 | if (index != -1 && index >= allocatedCount || buffer.position() == buffer.limit()) { 79 | resizeBuffer(); 80 | return false; 81 | } 82 | 83 | if (index != -1) { 84 | buffer.position(index * 9 * 4); 85 | } 86 | buffer.putFloat(point.x); 87 | buffer.putFloat(point.y); 88 | buffer.putFloat(z); 89 | buffer.putFloat(size); 90 | buffer.putFloat(angle); 91 | buffer.putFloat(alpha); 92 | buffer.putFloat(red); 93 | buffer.putFloat(green); 94 | buffer.putFloat(blue); 95 | 96 | return true; 97 | } 98 | 99 | public void reset() { 100 | count = 0; 101 | remainder = 0; 102 | if (buffer != null) { 103 | buffer.position(0); 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/views/components/paint/Shader.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.views.components.paint; 2 | 3 | import android.graphics.Color; 4 | import android.opengl.GLES20; 5 | import com.example.photopaint.helpers.BuildVars; 6 | import com.example.photopaint.helpers.FileLog; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | public class Shader { 12 | 13 | protected int program; 14 | private int vertexShader; 15 | private int fragmentShader; 16 | 17 | protected Map uniformsMap = new HashMap<>(); 18 | 19 | public Shader(String vertexShader, String fragmentShader, String attributes[], String uniforms[]) { 20 | this.program = GLES20.glCreateProgram(); 21 | 22 | CompilationResult vResult = compileShader(GLES20.GL_VERTEX_SHADER, vertexShader); 23 | if (vResult.status == GLES20.GL_FALSE) { 24 | if (BuildVars.LOGS_ENABLED) { 25 | FileLog.e("Vertex shader compilation failed"); 26 | } 27 | destroyShader(vResult.shader, 0, program); 28 | return; 29 | } 30 | 31 | CompilationResult fResult = compileShader(GLES20.GL_FRAGMENT_SHADER, fragmentShader); 32 | if (fResult.status == GLES20.GL_FALSE) { 33 | if (BuildVars.LOGS_ENABLED) { 34 | FileLog.e("Fragment shader compilation failed"); 35 | } 36 | destroyShader(vResult.shader, fResult.shader, program); 37 | return; 38 | } 39 | 40 | GLES20.glAttachShader(program, vResult.shader); 41 | GLES20.glAttachShader(program, fResult.shader); 42 | 43 | for (int i = 0; i < attributes.length; i++) { 44 | GLES20.glBindAttribLocation(program, i, attributes[i]); 45 | } 46 | 47 | if (linkProgram(program) == GLES20.GL_FALSE) { 48 | destroyShader(vResult.shader, fResult.shader, program); 49 | return; 50 | } 51 | 52 | for (String uniform : uniforms) { 53 | uniformsMap.put(uniform, GLES20.glGetUniformLocation(program, uniform)); 54 | } 55 | 56 | if (vResult.shader != 0) { 57 | GLES20.glDeleteShader(vResult.shader); 58 | } 59 | 60 | if (fResult.shader != 0) { 61 | GLES20.glDeleteShader(fResult.shader); 62 | } 63 | } 64 | 65 | public void cleanResources() { 66 | if (program != 0) { 67 | GLES20.glDeleteProgram(vertexShader); 68 | program = 0; 69 | } 70 | } 71 | 72 | private class CompilationResult { 73 | int shader; 74 | int status; 75 | 76 | CompilationResult(int shader, int status) { 77 | this.shader = shader; 78 | this.status = status; 79 | } 80 | } 81 | 82 | public int getUniform(String key) { 83 | return uniformsMap.get(key); 84 | } 85 | 86 | private CompilationResult compileShader(int type, String shaderCode) { 87 | int shader = GLES20.glCreateShader(type); 88 | GLES20.glShaderSource(shader, shaderCode); 89 | GLES20.glCompileShader(shader); 90 | 91 | int[] compileStatus = new int[1]; 92 | GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0); 93 | if (compileStatus[0] == GLES20.GL_FALSE) { 94 | if (BuildVars.LOGS_ENABLED) { 95 | FileLog.e(GLES20.glGetShaderInfoLog(shader)); 96 | } 97 | } 98 | 99 | return new CompilationResult(shader, compileStatus[0]); 100 | } 101 | 102 | private int linkProgram(int program) { 103 | GLES20.glLinkProgram(program); 104 | 105 | int[] linkStatus = new int[1]; 106 | GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); 107 | if (linkStatus[0] == GLES20.GL_FALSE) { 108 | if (BuildVars.LOGS_ENABLED) { 109 | FileLog.e(GLES20.glGetProgramInfoLog(program)); 110 | } 111 | } 112 | 113 | return linkStatus[0]; 114 | } 115 | 116 | private void destroyShader(int vertexShader, int fragmentShader, int program) { 117 | if (vertexShader != 0) { 118 | GLES20.glDeleteShader(vertexShader); 119 | } 120 | 121 | if (fragmentShader != 0) { 122 | GLES20.glDeleteShader(fragmentShader); 123 | } 124 | 125 | if (program != 0) { 126 | GLES20.glDeleteProgram(vertexShader); 127 | } 128 | } 129 | 130 | public static void SetColorUniform(int location, int color) { 131 | float r = Color.red(color) / 255.0f; 132 | float g = Color.green(color) / 255.0f; 133 | float b = Color.blue(color) / 255.0f; 134 | float a = Color.alpha(color) / 255.0f; 135 | 136 | GLES20.glUniform4f(location, r, g, b, a); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/views/components/paint/Slice.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.views.components.paint; 2 | 3 | import android.graphics.RectF; 4 | import com.example.photopaint.helpers.ApplicationLoader; 5 | import com.example.photopaint.helpers.DispatchQueue; 6 | import com.example.photopaint.helpers.FileLog; 7 | 8 | import java.io.ByteArrayOutputStream; 9 | import java.io.File; 10 | import java.io.FileInputStream; 11 | import java.io.FileOutputStream; 12 | import java.nio.ByteBuffer; 13 | import java.util.zip.Deflater; 14 | import java.util.zip.Inflater; 15 | 16 | public class Slice { 17 | private RectF bounds; 18 | private File file; 19 | 20 | public Slice(final ByteBuffer data, RectF rect, DispatchQueue queue) { 21 | bounds = rect; 22 | 23 | try { 24 | File outputDir = ApplicationLoader.applicationContext.getCacheDir(); 25 | file = File.createTempFile("paint", ".bin", outputDir); 26 | } catch (Exception e) { 27 | FileLog.e(e); 28 | } 29 | 30 | if (file == null) 31 | return; 32 | 33 | storeData(data); 34 | } 35 | 36 | public void cleanResources() { 37 | if (file != null) { 38 | file.delete(); 39 | file = null; 40 | } 41 | } 42 | 43 | private void storeData(ByteBuffer data) { 44 | try { 45 | final byte[] input = data.array(); 46 | FileOutputStream fos = new FileOutputStream(file); 47 | 48 | final Deflater deflater = new Deflater(Deflater.BEST_SPEED, true); 49 | deflater.setInput(input, data.arrayOffset(), data.remaining()); 50 | deflater.finish(); 51 | 52 | byte[] buf = new byte[1024]; 53 | while (!deflater.finished()) { 54 | int byteCount = deflater.deflate(buf); 55 | fos.write(buf, 0, byteCount); 56 | } 57 | deflater.end(); 58 | 59 | fos.close(); 60 | } catch (Exception e) { 61 | FileLog.e(e); 62 | } 63 | } 64 | 65 | public ByteBuffer getData() { 66 | try { 67 | byte[] input = new byte[1024]; 68 | byte[] output = new byte[1024]; 69 | FileInputStream fin = new FileInputStream(file); 70 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 71 | Inflater inflater = new Inflater(true); 72 | 73 | while (true) { 74 | int numRead = fin.read(input); 75 | if (numRead != -1) { 76 | inflater.setInput(input, 0, numRead); 77 | } 78 | 79 | int numDecompressed; 80 | while ((numDecompressed = inflater.inflate(output, 0, output.length)) != 0) { 81 | bos.write(output, 0, numDecompressed); 82 | } 83 | 84 | if (inflater.finished()) { 85 | break; 86 | } 87 | else if (inflater.needsInput()) { 88 | continue; 89 | } 90 | } 91 | 92 | inflater.end(); 93 | ByteBuffer result = ByteBuffer.wrap(bos.toByteArray(), 0, bos.size()); 94 | 95 | bos.close(); 96 | fin.close(); 97 | 98 | return result; 99 | } catch (Exception e) { 100 | FileLog.e(e); 101 | } 102 | 103 | return null; 104 | } 105 | 106 | public int getX() { 107 | return (int) bounds.left; 108 | } 109 | 110 | public int getY() { 111 | return (int) bounds.top; 112 | } 113 | 114 | public int getWidth() { 115 | return (int) bounds.width(); 116 | } 117 | 118 | public int getHeight() { 119 | return (int) bounds.height(); 120 | } 121 | 122 | public RectF getBounds() { 123 | return new RectF(bounds); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/views/components/paint/Swatch.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.views.components.paint; 2 | 3 | public class Swatch { 4 | 5 | public int color; 6 | public float colorLocation; 7 | public float brushWeight; 8 | 9 | public Swatch(int color, float colorLocation, float brushWeight) { 10 | this.color = color; 11 | this.colorLocation = colorLocation; 12 | this.brushWeight = brushWeight; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/views/components/paint/Texture.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.views.components.paint; 2 | 3 | import android.graphics.Bitmap; 4 | import android.opengl.GLES20; 5 | import android.opengl.GLUtils; 6 | import com.example.photopaint.views.components.Size; 7 | 8 | public class Texture { 9 | 10 | private Bitmap bitmap; 11 | private int texture; 12 | 13 | public Texture(Bitmap bitmap) { 14 | this.bitmap = bitmap; 15 | } 16 | 17 | public void cleanResources(boolean recycleBitmap) { 18 | if (texture == 0) { 19 | return; 20 | } 21 | 22 | int[] textures = new int[]{texture}; 23 | GLES20.glDeleteTextures(1, textures, 0); 24 | texture = 0; 25 | 26 | if (recycleBitmap) { 27 | bitmap.recycle(); 28 | } 29 | } 30 | 31 | private boolean isPOT(int x) { 32 | return (x & (x - 1)) == 0; 33 | } 34 | 35 | public int texture() { 36 | if (texture != 0) { 37 | return texture; 38 | } 39 | 40 | if (bitmap.isRecycled()) { 41 | return 0; 42 | } 43 | 44 | int[] textures = new int[1]; 45 | GLES20.glGenTextures(1, textures, 0); 46 | texture = textures[0]; 47 | 48 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture); 49 | 50 | boolean mipMappable = false; //isPOT(bitmap.getWidth()) && isPOT(bitmap.getHeight()); 51 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); 52 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); 53 | 54 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); 55 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, mipMappable ? GLES20.GL_LINEAR_MIPMAP_LINEAR : GLES20.GL_LINEAR); 56 | 57 | GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0); 58 | 59 | if (mipMappable) { 60 | GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D); 61 | } 62 | 63 | Utils.HasGLError(); 64 | 65 | return texture; 66 | } 67 | 68 | public static int generateTexture(Size size) { 69 | int texture; 70 | int[] textures = new int[1]; 71 | GLES20.glGenTextures(1, textures, 0); 72 | texture = textures[0]; 73 | 74 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture); 75 | 76 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); 77 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); 78 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); 79 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); 80 | 81 | int width = (int) size.width; 82 | int height = (int) size.height; 83 | int format = GLES20.GL_RGBA; 84 | int type = GLES20.GL_UNSIGNED_BYTE; 85 | 86 | GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, format, width, height, 0, format, type, null); 87 | 88 | return texture; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/views/components/paint/UndoStore.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.views.components.paint; 2 | 3 | import com.example.photopaint.helpers.AndroidUtilities; 4 | 5 | import java.util.*; 6 | 7 | public class UndoStore { 8 | 9 | public interface UndoStoreDelegate { 10 | void historyChanged(); 11 | } 12 | 13 | private UndoStoreDelegate delegate; 14 | private Map uuidToOperationMap = new HashMap<>(); 15 | private List operations = new ArrayList<>(); 16 | private Map uuidRecoverOperateMap = new HashMap<>(); 17 | private List recoverOperations = new Stack<>(); 18 | 19 | public boolean canUndo() { 20 | return !operations.isEmpty(); 21 | } 22 | 23 | public boolean canRecover(){ 24 | return !recoverOperations.isEmpty(); 25 | } 26 | 27 | public void setDelegate(UndoStoreDelegate undoStoreDelegate) { 28 | delegate = undoStoreDelegate; 29 | } 30 | 31 | public void registerUndo(UUID uuid, Runnable undoRunnable) { 32 | uuidToOperationMap.put(uuid, undoRunnable); 33 | operations.add(uuid); 34 | 35 | while (!recoverOperations.isEmpty()){ 36 | int lastIndex = recoverOperations.size() - 1; 37 | UUID deleteId = recoverOperations.get(lastIndex); 38 | 39 | operations.remove(deleteId); 40 | uuidToOperationMap.remove(deleteId); 41 | 42 | recoverOperations.remove(lastIndex); 43 | uuidRecoverOperateMap.remove(deleteId); 44 | } 45 | 46 | notifyOfHistoryChanges(); 47 | } 48 | 49 | public void unregisterUndo(UUID uuid) { 50 | uuidToOperationMap.remove(uuid); 51 | operations.remove(uuid); 52 | 53 | notifyOfHistoryChanges(); 54 | } 55 | 56 | public void registerRecover(UUID uuid, Runnable recoverRunnable){ 57 | uuidRecoverOperateMap.put(uuid, recoverRunnable); 58 | } 59 | 60 | public void unRegisterRecover(UUID uuid){ 61 | uuidRecoverOperateMap.remove(uuid); 62 | } 63 | 64 | public void undo() { 65 | if (operations.size() == 0) { 66 | return; 67 | } 68 | 69 | int lastIndex = operations.size() - 1; 70 | UUID uuid = operations.get(lastIndex); 71 | Runnable undoRunnable = uuidToOperationMap.get(uuid); 72 | 73 | operations.remove(lastIndex); 74 | recoverOperations.add(uuid); 75 | 76 | undoRunnable.run(); 77 | notifyOfHistoryChanges(); 78 | } 79 | 80 | public void recover() { 81 | if (!canRecover()) { 82 | return; 83 | } 84 | 85 | int lastIndex = recoverOperations.size() - 1; 86 | UUID uuid = recoverOperations.get(lastIndex); 87 | recoverOperations.remove(lastIndex); 88 | Runnable recoverRunnable = uuidRecoverOperateMap.get(uuid); 89 | 90 | operations.add(uuid); 91 | 92 | recoverRunnable.run(); 93 | notifyOfHistoryChanges(); 94 | } 95 | 96 | public void reset() { 97 | // 清掉图层并清空撤销栈 98 | for (int index = operations.size() - 1; index >=0; index--){ 99 | UUID uuid = operations.get(index); 100 | Runnable undoRunnable = uuidToOperationMap.get(uuid); 101 | uuidToOperationMap.remove(uuid); 102 | operations.remove(index); 103 | 104 | undoRunnable.run(); 105 | } 106 | 107 | notifyOfHistoryChanges(); 108 | } 109 | 110 | private void notifyOfHistoryChanges() { 111 | AndroidUtilities.runOnUIThread(new Runnable() { 112 | @Override 113 | public void run() { 114 | if (delegate != null) { 115 | delegate.historyChanged(); 116 | } 117 | } 118 | }); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/views/components/paint/Utils.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.views.components.paint; 2 | 3 | import android.graphics.RectF; 4 | import android.opengl.GLES20; 5 | import android.opengl.GLUtils; 6 | import android.util.Log; 7 | 8 | public class Utils { 9 | 10 | public static void HasGLError() { 11 | int error = GLES20.glGetError(); 12 | if (error != 0) { 13 | Log.d("Paint", GLUtils.getEGLErrorString(error)); 14 | } 15 | } 16 | 17 | public static void RectFIntegral(RectF rect) { 18 | rect.left = (int) Math.floor(rect.left); 19 | rect.top = (int) Math.floor(rect.top); 20 | rect.right = (int) Math.ceil(rect.right); 21 | rect.bottom = (int) Math.ceil(rect.bottom); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/views/components/paint/views/EditTextOutline.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.views.components.paint.views; 2 | 3 | import android.content.Context; 4 | import android.graphics.*; 5 | import android.text.Layout; 6 | import android.text.StaticLayout; 7 | import android.text.TextPaint; 8 | import android.widget.EditText; 9 | 10 | public class EditTextOutline extends EditText { 11 | 12 | private final Canvas mCanvas = new Canvas(); 13 | private final TextPaint mPaint = new TextPaint(); 14 | private Bitmap mCache; 15 | private boolean mUpdateCachedBitmap; 16 | private int mStrokeColor; 17 | private float mStrokeWidth; 18 | private boolean isEditing; 19 | 20 | public EditTextOutline(Context context) { 21 | super(context); 22 | 23 | mStrokeColor = Color.TRANSPARENT; 24 | 25 | mUpdateCachedBitmap = true; 26 | mPaint.setAntiAlias(true); 27 | mPaint.setStyle(Paint.Style.FILL_AND_STROKE); 28 | } 29 | 30 | protected void onTextChanged(CharSequence text, int start, int before, int after) { 31 | super.onTextChanged(text, start, before, after); 32 | mUpdateCachedBitmap = true; 33 | } 34 | 35 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 36 | super.onSizeChanged(w, h, oldw, oldh); 37 | if (w > 0 && h > 0) { 38 | mUpdateCachedBitmap = true; 39 | mCache = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); 40 | } else { 41 | mCache = null; 42 | } 43 | } 44 | 45 | public void setStrokeColor(int strokeColor) { 46 | this.mStrokeColor = strokeColor; 47 | mUpdateCachedBitmap = true; 48 | invalidate(); 49 | } 50 | 51 | public void setStrokeWidth(float strokeWidth) { 52 | this.mStrokeWidth = strokeWidth; 53 | mUpdateCachedBitmap = true; 54 | invalidate(); 55 | } 56 | 57 | public void setEditing(boolean isEditing){ 58 | this.isEditing = isEditing; 59 | invalidate(); 60 | } 61 | 62 | protected void onDraw(Canvas canvas) { 63 | if (!isEditing && mCache != null && mStrokeColor != Color.TRANSPARENT) { 64 | if (mUpdateCachedBitmap) { 65 | final int w = getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); 66 | final int h = getMeasuredHeight(); 67 | final String text = getText().toString(); 68 | 69 | mCanvas.setBitmap(mCache); 70 | mCanvas.drawColor(0, PorterDuff.Mode.CLEAR); 71 | 72 | float strokeWidth = mStrokeWidth > 0 ? mStrokeWidth : (float)Math.ceil(getTextSize() / 11.5f); 73 | mPaint.setStrokeWidth(strokeWidth); 74 | mPaint.setColor(Color.WHITE); 75 | mPaint.setTextSize(getTextSize()); 76 | mPaint.setTypeface(getTypeface()); 77 | mPaint.setStyle(Paint.Style.FILL_AND_STROKE); 78 | 79 | StaticLayout sl = new StaticLayout(text, mPaint, w, Layout.Alignment.ALIGN_CENTER, 1, 0, true); 80 | 81 | mCanvas.save(); 82 | float a = (h - getPaddingTop() - getPaddingBottom() - sl.getHeight()) / 2.0f; 83 | mCanvas.translate(getPaddingLeft(), a + getPaddingTop()); 84 | sl.draw(mCanvas); 85 | mCanvas.restore(); 86 | 87 | mUpdateCachedBitmap = false; 88 | } 89 | canvas.drawBitmap(mCache, 0, 0, mPaint); 90 | } 91 | setTextColor(isEditing ? Color.WHITE : mStrokeColor); 92 | super.onDraw(canvas); 93 | } 94 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/views/components/paint/views/EntitiesContainerView.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.views.components.paint.views; 2 | 3 | import android.content.Context; 4 | import android.view.MotionEvent; 5 | import android.view.ScaleGestureDetector; 6 | import android.view.View; 7 | import android.widget.FrameLayout; 8 | 9 | public class EntitiesContainerView extends FrameLayout implements ScaleGestureDetector.OnScaleGestureListener, RotationGestureDetector.OnRotationGestureListener { 10 | 11 | public interface EntitiesContainerViewDelegate { 12 | boolean shouldReceiveTouches(); 13 | void onEntityDeselect(); 14 | EntityView onSelectedEntityRequest(); 15 | } 16 | 17 | private EntitiesContainerViewDelegate delegate; 18 | private ScaleGestureDetector gestureDetector; 19 | private RotationGestureDetector rotationGestureDetector; 20 | private float previousScale = 1.0f; 21 | private float previousAngle; 22 | private boolean hasTransformed; 23 | 24 | public EntitiesContainerView(Context context, EntitiesContainerViewDelegate entitiesContainerViewDelegate) { 25 | super(context); 26 | 27 | gestureDetector = new ScaleGestureDetector(context, this); 28 | rotationGestureDetector = new RotationGestureDetector(this); 29 | delegate = entitiesContainerViewDelegate; 30 | } 31 | 32 | public int entitiesCount() { 33 | int count = 0; 34 | for (int index = 0; index < getChildCount(); index++) { 35 | View view = getChildAt(index); 36 | if (!(view instanceof EntityView)) { 37 | continue; 38 | } 39 | count++; 40 | } 41 | return count; 42 | } 43 | 44 | public void bringViewToFront(EntityView view) { 45 | if (indexOfChild(view) != getChildCount() - 1) { 46 | removeView(view); 47 | addView(view, getChildCount()); 48 | } 49 | } 50 | 51 | @Override 52 | public boolean onInterceptTouchEvent(MotionEvent ev) { 53 | return ev.getPointerCount() == 2 && delegate.shouldReceiveTouches(); 54 | } 55 | 56 | @Override 57 | public boolean onTouchEvent(MotionEvent event) { 58 | EntityView selectedEntity = delegate.onSelectedEntityRequest(); 59 | if (selectedEntity == null) { 60 | return false; 61 | } 62 | 63 | if (event.getPointerCount() == 1) 64 | { 65 | int action = event.getActionMasked(); 66 | if (action == MotionEvent.ACTION_DOWN) { 67 | hasTransformed = false; 68 | } 69 | else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_MOVE) { 70 | if (!hasTransformed && delegate != null) { 71 | delegate.onEntityDeselect(); 72 | } 73 | return false; 74 | } 75 | } 76 | 77 | gestureDetector.onTouchEvent(event); 78 | rotationGestureDetector.onTouchEvent(event); 79 | return true; 80 | } 81 | 82 | @Override 83 | public boolean onScale(ScaleGestureDetector detector) { 84 | float sf = detector.getScaleFactor(); 85 | float newScale = sf / previousScale; 86 | 87 | EntityView view = delegate.onSelectedEntityRequest(); 88 | view.scale(newScale); 89 | 90 | previousScale = sf; 91 | 92 | return false; 93 | } 94 | 95 | @Override 96 | public boolean onScaleBegin(ScaleGestureDetector detector) { 97 | previousScale = 1.0f; 98 | hasTransformed = true; 99 | return true; 100 | } 101 | 102 | @Override 103 | public void onScaleEnd(ScaleGestureDetector detector) { 104 | 105 | } 106 | 107 | @Override 108 | public void onRotationBegin(RotationGestureDetector rotationDetector) { 109 | previousAngle = rotationDetector.getStartAngle(); 110 | hasTransformed = true; 111 | } 112 | 113 | @Override 114 | public void onRotation(RotationGestureDetector rotationDetector) { 115 | EntityView view = delegate.onSelectedEntityRequest(); 116 | float angle = rotationDetector.getAngle(); 117 | float delta = previousAngle - angle; 118 | view.rotate(view.getRotation() + delta); 119 | previousAngle = angle; 120 | } 121 | 122 | @Override 123 | public void onRotationEnd(RotationGestureDetector rotationDetector) { 124 | 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/views/components/paint/views/RotationGestureDetector.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.views.components.paint.views; 2 | 3 | import android.view.MotionEvent; 4 | 5 | public class RotationGestureDetector { 6 | 7 | public interface OnRotationGestureListener { 8 | void onRotationBegin(RotationGestureDetector rotationDetector); 9 | void onRotation(RotationGestureDetector rotationDetector); 10 | void onRotationEnd(RotationGestureDetector rotationDetector); 11 | } 12 | 13 | private float fX, fY, sX, sY; 14 | private float angle; 15 | private float startAngle; 16 | 17 | private OnRotationGestureListener mListener; 18 | 19 | public float getAngle() { 20 | return angle; 21 | } 22 | 23 | public float getStartAngle() { 24 | return startAngle; 25 | } 26 | 27 | public RotationGestureDetector(OnRotationGestureListener listener) { 28 | mListener = listener; 29 | } 30 | 31 | public boolean onTouchEvent(MotionEvent event) { 32 | if (event.getPointerCount() != 2) 33 | return false; 34 | 35 | switch (event.getActionMasked()) { 36 | case MotionEvent.ACTION_DOWN: 37 | case MotionEvent.ACTION_POINTER_DOWN: { 38 | sX = event.getX(0); 39 | sY = event.getY(0); 40 | fX = event.getX(1); 41 | fY = event.getY(1); 42 | } 43 | break; 44 | 45 | case MotionEvent.ACTION_MOVE: { 46 | float nfX, nfY, nsX, nsY; 47 | nsX = event.getX(0); 48 | nsY = event.getY(0); 49 | nfX = event.getX(1); 50 | nfY = event.getY(1); 51 | 52 | angle = angleBetweenLines(fX, fY, sX, sY, nfX, nfY, nsX, nsY); 53 | 54 | if (mListener != null) { 55 | if (Float.isNaN(startAngle)) { 56 | startAngle = angle; 57 | mListener.onRotationBegin(this); 58 | } else { 59 | mListener.onRotation(this); 60 | } 61 | } 62 | } 63 | break; 64 | 65 | case MotionEvent.ACTION_UP: 66 | case MotionEvent.ACTION_CANCEL: { 67 | startAngle = Float.NaN; 68 | } 69 | break; 70 | 71 | case MotionEvent.ACTION_POINTER_UP: { 72 | startAngle = Float.NaN; 73 | 74 | if (mListener != null) { 75 | mListener.onRotationEnd(this); 76 | } 77 | } 78 | break; 79 | } 80 | return true; 81 | } 82 | 83 | private float angleBetweenLines(float fX, float fY, float sX, float sY, float nfX, float nfY, float nsX, float nsY) { 84 | float angle1 = (float) Math.atan2((fY - sY), (fX - sX)); 85 | float angle2 = (float) Math.atan2((nfY - nsY), (nfX - nsX)); 86 | float angle = ((float) Math.toDegrees(angle1 - angle2)) % 360; 87 | if (angle < -180.f) { 88 | angle += 360.0f; 89 | } 90 | if (angle > 180.f) { 91 | angle -= 360.0f; 92 | } 93 | return angle; 94 | } 95 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/views/components/paint/views/StickerView.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.views.components.paint.views; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.graphics.Canvas; 7 | import android.graphics.Paint; 8 | import android.graphics.RectF; 9 | import android.view.ViewGroup; 10 | import android.widget.FrameLayout; 11 | 12 | import com.example.photopaint.helpers.AndroidUtilities; 13 | //import org.telegram.messenger.FileLoader; 14 | //import org.telegram.messenger.ImageLocation; 15 | //import org.telegram.messenger.ImageReceiver; 16 | import com.example.photopaint.R; 17 | import com.example.photopaint.views.components.LayoutHelper; 18 | import com.example.photopaint.views.components.Point; 19 | import com.example.photopaint.views.components.Rect; 20 | import com.example.photopaint.views.components.Size; 21 | 22 | public class StickerView extends EntityView { 23 | 24 | private class FrameLayoutDrawer extends FrameLayout { 25 | public FrameLayoutDrawer(Context context) { 26 | super(context); 27 | setWillNotDraw(false); 28 | } 29 | 30 | @Override 31 | protected void onDraw(Canvas canvas) { 32 | StickerView.this.stickerDraw(canvas); 33 | } 34 | } 35 | 36 | private Bitmap sticker; 37 | private Object parentObject; 38 | private int anchor = -1; 39 | private boolean mirrored = false; 40 | private Size baseSize; 41 | 42 | private FrameLayoutDrawer containerView; 43 | // private ImageReceiver centerImage = new ImageReceiver(); 44 | 45 | public StickerView(Context context, Point position, Size baseSize, Bitmap sticker, Object parentObject) { 46 | this(context, position, 0.0f, 1.0f, baseSize, sticker, parentObject); 47 | } 48 | 49 | public StickerView(Context context, Point position, float angle, float scale, Size baseSize, Bitmap sticker, Object parentObject) { 50 | super(context, position); 51 | setRotation(angle); 52 | setScale(scale); 53 | 54 | this.sticker = sticker; 55 | this.baseSize = baseSize; 56 | this.parentObject = parentObject; 57 | 58 | // for (int a = 0; a < sticker.attributes.size(); a++) { 59 | // TLRPC.DocumentAttribute attribute = sticker.attributes.get(a); 60 | // if (attribute instanceof TLRPC.TL_documentAttributeSticker) { 61 | // if (attribute.mask_coords != null) 62 | // anchor = attribute.mask_coords.n; 63 | // break; 64 | // } 65 | // } 66 | 67 | containerView = new FrameLayoutDrawer(context); 68 | addView(containerView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); 69 | 70 | // centerImage.setAspectFit(true); 71 | // centerImage.setInvalidateAll(true); 72 | // centerImage.setParentView(containerView); 73 | // TLRPC.PhotoSize thumb = FileLoader.getClosestPhotoSizeWithSize(sticker.thumbs, 90); 74 | // centerImage.setImage(ImageLocation.getForDocument(sticker), null, ImageLocation.getForDocument(thumb, sticker), null, "webp", parentObject, 1); 75 | 76 | updatePosition(); 77 | } 78 | 79 | public StickerView(Context context, StickerView stickerView, Point position) { 80 | this(context, position, stickerView.getRotation(), stickerView.getScale(), stickerView.baseSize, stickerView.sticker, stickerView.parentObject); 81 | if (stickerView.mirrored) { 82 | mirror(); 83 | } 84 | } 85 | 86 | public int getAnchor() { 87 | return anchor; 88 | } 89 | 90 | public void mirror() { 91 | mirrored = !mirrored; 92 | containerView.invalidate(); 93 | } 94 | 95 | protected void updatePosition() { 96 | float halfWidth = baseSize.width / 2.0f; 97 | float halfHeight = baseSize.height / 2.0f; 98 | setX(position.x - halfWidth); 99 | setY(position.y - halfHeight); 100 | updateSelectionView(); 101 | } 102 | 103 | protected void stickerDraw(Canvas canvas) { 104 | if (containerView == null) { 105 | return; 106 | } 107 | 108 | canvas.save(); 109 | // Bitmap bitmap = centerImage.getBitmap(); 110 | Bitmap stickerImage = BitmapFactory.decodeResource(getResources(), R.drawable.sticker_demo); 111 | if (stickerImage != null) { 112 | if (mirrored) { 113 | canvas.scale(-1.0f, 1.0f); 114 | canvas.translate(-baseSize.width, 0); 115 | } 116 | // centerImage.setImageCoords(0, 0, (int) baseSize.width, (int) baseSize.height); 117 | // centerImage.draw(canvas); 118 | 119 | // float left = (baseSize.width - bitmap.getWidth())/2; 120 | // float top = (baseSize.height - bitmap.getHeight()) / 2; 121 | // canvas.drawBitmap(bitmap,left,top, new Paint()); 122 | 123 | float stickImageW = stickerImage.getWidth() ; 124 | float stickImageH = stickerImage.getHeight(); 125 | // float scaleW = stickImageW == 0 ? 1f : baseSize.width / stickImageW; 126 | // float scaleH = stickImageH == 0 ? 1f : baseSize.height /stickImageH; 127 | // float scale = Math.min(scaleW, scaleH); 128 | // 129 | // stickImageW *= scale; 130 | // stickImageH *= scale; 131 | canvas.drawBitmap(stickerImage,(baseSize.width - stickImageW) / 2 ,(baseSize.height - stickImageH) / 2, new Paint()); 132 | } 133 | canvas.restore(); 134 | } 135 | 136 | @Override 137 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 138 | super.onMeasure(MeasureSpec.makeMeasureSpec((int) baseSize.width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec((int) baseSize.height, MeasureSpec.EXACTLY)); 139 | } 140 | 141 | @Override 142 | protected Rect getSelectionBounds() { 143 | ViewGroup parentView = (ViewGroup) getParent(); 144 | float scale = parentView.getScaleX(); 145 | 146 | float side = getWidth() * (getScale() + 0.4f); 147 | return new Rect((position.x - side / 2.0f) * scale, (position.y - side / 2.0f) * scale, side * scale, side * scale); 148 | } 149 | 150 | @Override 151 | protected SelectionView createSelectionView() { 152 | return new StickerViewSelectionView(getContext()); 153 | } 154 | 155 | public Bitmap getSticker() { 156 | return sticker; 157 | } 158 | 159 | public class StickerViewSelectionView extends SelectionView { 160 | 161 | private Paint arcPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 162 | private RectF arcRect = new RectF(); 163 | 164 | public StickerViewSelectionView(Context context) { 165 | super(context); 166 | 167 | arcPaint.setColor(0xffffffff); 168 | arcPaint.setStrokeWidth(AndroidUtilities.dp(1)); 169 | arcPaint.setStyle(Paint.Style.STROKE); 170 | } 171 | 172 | @Override 173 | protected int pointInsideHandle(float x, float y) { 174 | float thickness = AndroidUtilities.dp(1.0f); 175 | float radius = AndroidUtilities.dp(19.5f); 176 | 177 | float inset = radius + thickness; 178 | float middle = inset + (getHeight() - inset * 2) / 2.0f; 179 | 180 | if (x > inset - radius && y > middle - radius && x < inset + radius && y < middle + radius) { 181 | return SELECTION_LEFT_HANDLE; 182 | } else if (x > inset + (getWidth() - inset * 2) - radius && y > middle - radius && x < inset + (getWidth() - inset * 2) + radius && y < middle + radius) { 183 | return SELECTION_RIGHT_HANDLE; 184 | } 185 | 186 | float selectionRadius = getWidth() / 2.0f; 187 | 188 | if (Math.pow(x - selectionRadius, 2) + Math.pow(y - selectionRadius, 2) < Math.pow(selectionRadius, 2)) { 189 | return SELECTION_WHOLE_HANDLE; 190 | } 191 | 192 | return 0; 193 | } 194 | 195 | @Override 196 | protected void onDraw(Canvas canvas) { 197 | super.onDraw(canvas); 198 | 199 | float thickness = AndroidUtilities.dp(1.0f); 200 | float radius = AndroidUtilities.dp(4.5f); 201 | 202 | float inset = radius + thickness + AndroidUtilities.dp(15); 203 | float mainRadius = getWidth() / 2 - inset; 204 | 205 | float space = 4.0f; 206 | float length = 4.0f; 207 | 208 | arcRect.set(inset, inset, inset + mainRadius * 2, inset + mainRadius * 2); 209 | for (int i = 0; i < 48; i++) { 210 | canvas.drawArc(arcRect, i * (space + length), length, false, arcPaint); 211 | } 212 | 213 | canvas.drawCircle(inset, inset + mainRadius, radius, dotPaint); 214 | canvas.drawCircle(inset, inset + mainRadius, radius, dotStrokePaint); 215 | 216 | canvas.drawCircle(inset + mainRadius * 2, inset + mainRadius, radius, dotPaint); 217 | canvas.drawCircle(inset + mainRadius * 2, inset + mainRadius, radius, dotStrokePaint); 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/photopaint/views/components/paint/views/TextPaintView.java: -------------------------------------------------------------------------------- 1 | package com.example.photopaint.views.components.paint.views; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Typeface; 7 | import android.os.Build; 8 | import android.text.Editable; 9 | import android.text.Layout; 10 | import android.text.TextWatcher; 11 | import android.util.TypedValue; 12 | import android.view.Gravity; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | import android.view.inputmethod.EditorInfo; 16 | import android.view.inputmethod.InputMethodManager; 17 | import com.example.photopaint.helpers.AndroidUtilities; 18 | import com.example.photopaint.views.components.LayoutHelper; 19 | import com.example.photopaint.views.components.paint.Swatch; 20 | import com.example.photopaint.views.components.Point; 21 | import com.example.photopaint.views.components.Rect; 22 | 23 | public class TextPaintView extends EntityView { 24 | 25 | private EditTextOutline editText; 26 | private Swatch swatch; 27 | private boolean stroke; 28 | private int baseFontSize; 29 | 30 | public TextPaintView(Context context, Point position, int fontSize, String text, Swatch swatch, boolean stroke) { 31 | super(context, position); 32 | 33 | baseFontSize = fontSize; 34 | 35 | editText = new EditTextOutline(context); 36 | editText.setBackgroundColor(Color.TRANSPARENT); 37 | editText.setPadding(AndroidUtilities.dp(7), AndroidUtilities.dp(7), AndroidUtilities.dp(7), AndroidUtilities.dp(7)); 38 | editText.setClickable(false); 39 | editText.setEnabled(false); 40 | editText.setTextSize(TypedValue.COMPLEX_UNIT_PX, baseFontSize); 41 | editText.setText(text); 42 | editText.setTextColor(swatch.color); 43 | editText.setTypeface(null, Typeface.BOLD); 44 | editText.setGravity(Gravity.CENTER); 45 | editText.setHorizontallyScrolling(false); 46 | editText.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); 47 | editText.setFocusableInTouchMode(true); 48 | editText.setInputType(editText.getInputType() | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES); 49 | addView(editText, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP)); 50 | 51 | if (Build.VERSION.SDK_INT >= 23) { 52 | editText.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE); 53 | } 54 | 55 | setSwatch(swatch); 56 | setStroke(stroke); 57 | 58 | updatePosition(); 59 | 60 | editText.addTextChangedListener(new TextWatcher() { 61 | private String text; 62 | private int beforeCursorPosition = 0; 63 | 64 | @Override 65 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 66 | text = s.toString(); 67 | beforeCursorPosition = start; 68 | } 69 | 70 | @Override 71 | public void onTextChanged(CharSequence s, int start, int before, int count) { 72 | 73 | } 74 | 75 | @Override 76 | public void afterTextChanged(Editable s) { 77 | editText.removeTextChangedListener(this); 78 | 79 | if (editText.getLineCount() > 9) { 80 | editText.setText(text); 81 | editText.setSelection(beforeCursorPosition); 82 | } 83 | 84 | editText.addTextChangedListener(this); 85 | } 86 | }); 87 | } 88 | 89 | public TextPaintView(Context context, TextPaintView textPaintView, Point position) { 90 | this(context, position, textPaintView.baseFontSize, textPaintView.getText(), textPaintView.getSwatch(), textPaintView.stroke); 91 | setRotation(textPaintView.getRotation()); 92 | setScale(textPaintView.getScale()); 93 | } 94 | 95 | public void setMaxWidth(int maxWidth) { 96 | editText.setMaxWidth(maxWidth); 97 | } 98 | 99 | @Override 100 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 101 | super.onLayout(changed, left, top, right, bottom); 102 | updatePosition(); 103 | } 104 | 105 | public String getText() { 106 | return editText.getText().toString(); 107 | } 108 | 109 | public void setText(String text) { 110 | editText.setText(text); 111 | } 112 | 113 | public View getFocusedView() { 114 | return editText; 115 | } 116 | 117 | public void beginEditing() { 118 | editText.setEnabled(true); 119 | editText.setClickable(true); 120 | editText.requestFocus(); 121 | editText.setSelection(editText.getText().length()); 122 | editText.setEditing(true); 123 | showKeyboard(); 124 | } 125 | 126 | public void endEditing() { 127 | editText.clearFocus(); 128 | editText.setEnabled(false); 129 | editText.setClickable(false); 130 | updateSelectionView(); 131 | editText.setEditing(false); 132 | hideKeyboard(); 133 | } 134 | 135 | private boolean showKeyboard() { 136 | try { 137 | InputMethodManager inputManager = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 138 | return inputManager.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT); 139 | } catch (Exception e) { 140 | // FileLog.e(e); 141 | } 142 | return false; 143 | } 144 | 145 | public void hideKeyboard() { 146 | try { 147 | InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 148 | if (!imm.isActive()) { 149 | return; 150 | } 151 | imm.hideSoftInputFromWindow(getWindowToken(), 0); 152 | } catch (Exception e) { 153 | // FileLog.e(e); 154 | } 155 | } 156 | 157 | public Swatch getSwatch() { 158 | return swatch; 159 | } 160 | 161 | public void setSwatch(Swatch swatch) { 162 | this.swatch = swatch; 163 | updateColor(); 164 | } 165 | 166 | public void setStroke(boolean stroke) { 167 | this.stroke = stroke; 168 | updateColor(); 169 | } 170 | 171 | private void updateColor() { 172 | if (stroke) { 173 | editText.setTextColor(0xffffffff); 174 | editText.setStrokeColor(swatch.color); 175 | editText.setShadowLayer(0, 0, 0, 0); 176 | } else { 177 | editText.setTextColor(swatch.color); 178 | editText.setStrokeColor(Color.TRANSPARENT); 179 | editText.setShadowLayer(8, 0, 2, 0xaa000000); 180 | } 181 | } 182 | 183 | @Override 184 | protected Rect getSelectionBounds() { 185 | ViewGroup parentView = (ViewGroup) getParent(); 186 | float scale = parentView.getScaleX(); 187 | float width = getWidth() * (getScale()) + AndroidUtilities.dp(46) / scale; 188 | float height = getHeight() * (getScale()) + AndroidUtilities.dp(20) / scale; 189 | return new Rect((position.x - width / 2.0f) * scale, (position.y - height / 2.0f) * scale, width * scale, height * scale); 190 | } 191 | 192 | protected TextViewSelectionView createSelectionView() { 193 | return new TextViewSelectionView(getContext()); 194 | } 195 | 196 | public class TextViewSelectionView extends SelectionView { 197 | 198 | public TextViewSelectionView(Context context) { 199 | super(context); 200 | } 201 | 202 | @Override 203 | protected int pointInsideHandle(float x, float y) { 204 | float thickness = AndroidUtilities.dp(1.0f); 205 | float radius = AndroidUtilities.dp(19.5f); 206 | 207 | float inset = radius + thickness; 208 | float width = getWidth() - inset * 2; 209 | float height = getHeight() - inset * 2; 210 | 211 | float middle = inset + height / 2.0f; 212 | 213 | if (x > inset - radius && y > middle - radius && x < inset + radius && y < middle + radius) { 214 | return SELECTION_LEFT_HANDLE; 215 | } else if (x > inset + width - radius && y > middle - radius && x < inset + width + radius && y < middle + radius) { 216 | return SELECTION_RIGHT_HANDLE; 217 | } 218 | 219 | if (x > inset && x < width && y > inset && y < height) { 220 | return SELECTION_WHOLE_HANDLE; 221 | } 222 | 223 | return 0; 224 | } 225 | 226 | @Override 227 | protected void onDraw(Canvas canvas) { 228 | super.onDraw(canvas); 229 | 230 | float space = AndroidUtilities.dp(3.0f); 231 | float length = AndroidUtilities.dp(3.0f); 232 | float thickness = AndroidUtilities.dp(1.0f); 233 | float radius = AndroidUtilities.dp(4.5f); 234 | 235 | float inset = radius + thickness + AndroidUtilities.dp(15); 236 | 237 | float width = getWidth() - inset * 2; 238 | float height = getHeight() - inset * 2; 239 | 240 | int xCount = (int) (Math.floor(width / (space + length))); 241 | float xGap = (float) Math.ceil(((width - xCount * (space + length)) + space) / 2.0f); 242 | 243 | for (int i = 0; i < xCount; i++) { 244 | float x = xGap + inset + i * (length + space); 245 | canvas.drawRect(x, inset - thickness / 2.0f, x + length, inset + thickness / 2.0f, paint); 246 | canvas.drawRect(x, inset + height - thickness / 2.0f, x + length, inset + height + thickness / 2.0f, paint); 247 | } 248 | 249 | int yCount = (int) (Math.floor(height / (space + length))); 250 | float yGap = (float) Math.ceil(((height - yCount * (space + length)) + space) / 2.0f); 251 | 252 | for (int i = 0; i < yCount; i++) { 253 | float y = yGap + inset + i * (length + space); 254 | canvas.drawRect(inset - thickness / 2.0f, y, inset + thickness / 2.0f, y + length, paint); 255 | canvas.drawRect(inset + width - thickness / 2.0f, y, inset + width + thickness / 2.0f, y + length, paint); 256 | } 257 | 258 | canvas.drawCircle(inset, inset + height / 2.0f, radius, dotPaint); 259 | canvas.drawCircle(inset, inset + height / 2.0f, radius, dotStrokePaint); 260 | 261 | canvas.drawCircle(inset + width, inset + height / 2.0f, radius, dotPaint); 262 | canvas.drawCircle(inset + width, inset + height / 2.0f, radius, dotStrokePaint); 263 | } 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /app/src/main/res/anim/popup_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/anim/popup_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/header_shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-hdpi/header_shadow.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/header_shadow_reverse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-hdpi/header_shadow_reverse.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_ab_done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-hdpi/ic_ab_done.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/knob_shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-hdpi/knob_shadow.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/media_doc_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-hdpi/media_doc_blue.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/media_doc_blue_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-hdpi/media_doc_blue_b.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/media_doc_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-hdpi/media_doc_green.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/media_doc_green_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-hdpi/media_doc_green_b.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/media_doc_load.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-hdpi/media_doc_load.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/media_doc_pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-hdpi/media_doc_pause.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/media_doc_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-hdpi/media_doc_red.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/media_doc_red_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-hdpi/media_doc_red_b.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/media_doc_yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-hdpi/media_doc_yellow.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/media_doc_yellow_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-hdpi/media_doc_yellow_b.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/photo_flip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-hdpi/photo_flip.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/photo_mosaic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-hdpi/photo_mosaic.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/photo_outline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-hdpi/photo_outline.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/photo_paint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-hdpi/photo_paint.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/photo_paint_brush.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-hdpi/photo_paint_brush.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/photo_paint_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-hdpi/photo_paint_text.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/photo_recover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-hdpi/photo_recover.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/photo_sticker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-hdpi/photo_sticker.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/photo_undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-hdpi/photo_undo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/popup_fixed_alert.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-hdpi/popup_fixed_alert.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/popup_fixed_alert2.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-hdpi/popup_fixed_alert2.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/popup_fixed_top.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-hdpi/popup_fixed_top.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/header_shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-mdpi/header_shadow.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/header_shadow_reverse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-mdpi/header_shadow_reverse.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_ab_done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-mdpi/ic_ab_done.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/knob_shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-mdpi/knob_shadow.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/media_doc_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-mdpi/media_doc_blue.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/media_doc_blue_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-mdpi/media_doc_blue_b.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/media_doc_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-mdpi/media_doc_green.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/media_doc_green_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-mdpi/media_doc_green_b.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/media_doc_load.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-mdpi/media_doc_load.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/media_doc_pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-mdpi/media_doc_pause.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/media_doc_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-mdpi/media_doc_red.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/media_doc_red_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-mdpi/media_doc_red_b.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/media_doc_yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-mdpi/media_doc_yellow.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/media_doc_yellow_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-mdpi/media_doc_yellow_b.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/photo_flip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-mdpi/photo_flip.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/photo_mosaic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-mdpi/photo_mosaic.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/photo_outline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-mdpi/photo_outline.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/photo_paint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-mdpi/photo_paint.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/photo_paint_brush.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-mdpi/photo_paint_brush.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/photo_paint_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-mdpi/photo_paint_text.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/photo_recover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-mdpi/photo_recover.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/photo_sticker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-mdpi/photo_sticker.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/photo_undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-mdpi/photo_undo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/popup_fixed_alert.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-mdpi/popup_fixed_alert.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/popup_fixed_alert2.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-mdpi/popup_fixed_alert2.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/popup_fixed_top.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-mdpi/popup_fixed_top.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/header_shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xhdpi/header_shadow.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/header_shadow_reverse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xhdpi/header_shadow_reverse.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_ab_done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xhdpi/ic_ab_done.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/knob_shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xhdpi/knob_shadow.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/media_doc_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xhdpi/media_doc_blue.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/media_doc_blue_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xhdpi/media_doc_blue_b.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/media_doc_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xhdpi/media_doc_green.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/media_doc_green_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xhdpi/media_doc_green_b.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/media_doc_load.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xhdpi/media_doc_load.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/media_doc_pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xhdpi/media_doc_pause.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/media_doc_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xhdpi/media_doc_red.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/media_doc_red_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xhdpi/media_doc_red_b.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/media_doc_yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xhdpi/media_doc_yellow.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/media_doc_yellow_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xhdpi/media_doc_yellow_b.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/photo_flip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xhdpi/photo_flip.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/photo_mosaic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xhdpi/photo_mosaic.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/photo_outline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xhdpi/photo_outline.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/photo_paint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xhdpi/photo_paint.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/photo_paint_brush.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xhdpi/photo_paint_brush.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/photo_paint_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xhdpi/photo_paint_text.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/photo_recover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xhdpi/photo_recover.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/photo_sticker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xhdpi/photo_sticker.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/photo_undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xhdpi/photo_undo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/popup_fixed_alert.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xhdpi/popup_fixed_alert.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/popup_fixed_alert2.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xhdpi/popup_fixed_alert2.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/popup_fixed_top.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xhdpi/popup_fixed_top.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/adv_img_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xxhdpi/adv_img_2.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/header_shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xxhdpi/header_shadow.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/header_shadow_reverse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xxhdpi/header_shadow_reverse.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_ab_done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xxhdpi/ic_ab_done.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/knob_shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xxhdpi/knob_shadow.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/media_doc_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xxhdpi/media_doc_blue.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/media_doc_blue_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xxhdpi/media_doc_blue_b.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/media_doc_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xxhdpi/media_doc_green.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/media_doc_green_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xxhdpi/media_doc_green_b.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/media_doc_load.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xxhdpi/media_doc_load.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/media_doc_pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xxhdpi/media_doc_pause.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/media_doc_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xxhdpi/media_doc_red.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/media_doc_red_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xxhdpi/media_doc_red_b.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/media_doc_yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xxhdpi/media_doc_yellow.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/media_doc_yellow_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xxhdpi/media_doc_yellow_b.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/photo_flip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xxhdpi/photo_flip.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/photo_mosaic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xxhdpi/photo_mosaic.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/photo_outline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xxhdpi/photo_outline.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/photo_paint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xxhdpi/photo_paint.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/photo_paint_brush.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xxhdpi/photo_paint_brush.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/photo_paint_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xxhdpi/photo_paint_text.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/photo_recover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xxhdpi/photo_recover.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/photo_sticker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xxhdpi/photo_sticker.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/photo_undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xxhdpi/photo_undo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/popup_fixed_alert.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xxhdpi/popup_fixed_alert.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/popup_fixed_alert2.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xxhdpi/popup_fixed_alert2.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/popup_fixed_top.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable-xxhdpi/popup_fixed_top.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/adv_img_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable/adv_img_2.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/catstile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable/catstile.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/field_carret_empty.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mosaic_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable/mosaic_black.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/mosaic_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable/mosaic_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/paint_elliptical_brush.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable/paint_elliptical_brush.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/paint_elliptical_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable/paint_elliptical_preview.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/paint_neon_brush.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable/paint_neon_brush.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/paint_neon_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable/paint_neon_preview.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/paint_radial_brush.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable/paint_radial_brush.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/paint_radial_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable/paint_radial_preview.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable/screenshot.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/sticker_demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/drawable/sticker_demo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/transparent.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | PhotoPaint 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 16 | 17 | 24 | 25 | 28 | 29 | 38 | 39 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/test/java/com/example/photopaint/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.photopaint 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.3.31' 5 | repositories { 6 | google() 7 | jcenter() 8 | 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.4.1' 12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | 23 | } 24 | } 25 | 26 | task clean(type: Delete) { 27 | delete rootProject.buildDir 28 | } 29 | -------------------------------------------------------------------------------- /gifs/1571733722289.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/gifs/1571733722289.gif -------------------------------------------------------------------------------- /gifs/1571733767440.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/gifs/1571733767440.gif -------------------------------------------------------------------------------- /gifs/1571733806801.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/gifs/1571733806801.gif -------------------------------------------------------------------------------- /gifs/1571733849769.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/gifs/1571733849769.gif -------------------------------------------------------------------------------- /gifs/1571733896635.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/gifs/1571733896635.gif -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # Kotlin code style for this project: "official" or "obsolete": 15 | kotlin.code.style=official 16 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rave-Tian/PhotoPaint/0a715c9845510183a73fefa8aa9af798e240706a/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Jul 15 17:35:18 CST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------