├── .gitignore ├── LICENSE ├── README.md ├── canvaseditor ├── .gitignore ├── bintray.gradle ├── build.gradle ├── canvaseditor.iml ├── consumer-rules.pro ├── install.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── outsbook │ │ └── libs │ │ └── canvaseditor │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── outsbook │ │ │ └── libs │ │ │ └── canvaseditor │ │ │ ├── CanvasEditorView.kt │ │ │ ├── constants │ │ │ ├── ActionMode.kt │ │ │ ├── ConstantSticker.kt │ │ │ └── ConstantStickerIcon.kt │ │ │ ├── enums │ │ │ └── DrawType.kt │ │ │ ├── events │ │ │ ├── DeleteIconEvent.kt │ │ │ ├── DoneIconEvent.kt │ │ │ ├── FlipIconEvent.kt │ │ │ └── ZoomIconEvent.kt │ │ │ ├── listeners │ │ │ ├── CanvasEditorListener.kt │ │ │ ├── PaintViewListener.kt │ │ │ ├── StickerIconListener.kt │ │ │ └── StickerViewListener.kt │ │ │ ├── models │ │ │ ├── DrawObject.kt │ │ │ └── PathAndPaint.kt │ │ │ ├── paints │ │ │ └── PaintView.kt │ │ │ └── stickers │ │ │ ├── BitmapSticker.kt │ │ │ ├── DrawableSticker.kt │ │ │ ├── Sticker.kt │ │ │ ├── StickerIcon.kt │ │ │ ├── StickerView.kt │ │ │ └── TextSticker.kt │ └── res │ │ └── drawable │ │ ├── ic_close_white_20dp.xml │ │ ├── ic_done_white_20dp.xml │ │ ├── ic_flip_white_20dp.xml │ │ ├── ic_rotate_scale_white_17dp.xml │ │ └── shape_transfarent_background.xml │ └── test │ └── java │ └── com │ └── outsbook │ └── libs │ └── canvaseditor │ └── ExampleUnitTest.kt ├── example-java ├── .gitignore ├── app │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── outsbook │ │ │ └── examplejava │ │ │ └── ExampleInstrumentedTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── outsbook │ │ │ │ └── examplejava │ │ │ │ └── MainActivity.java │ │ └── res │ │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── drawable │ │ │ ├── app_icon.png │ │ │ ├── ic_cancel_yellow_24dp.xml │ │ │ ├── ic_color_black_24dp.xml │ │ │ ├── ic_color_yellow_24dp.xml │ │ │ ├── ic_delete_white_24dp.xml │ │ │ ├── ic_download_gray_24dp.xml │ │ │ ├── ic_launcher_background.xml │ │ │ ├── ic_minus_black_24dp.xml │ │ │ ├── ic_minus_yellow_24dp.xml │ │ │ ├── ic_panorama_240dp.xml │ │ │ ├── ic_plus_black_24dp.xml │ │ │ ├── ic_plus_yellow_24dp.xml │ │ │ ├── ic_redo_white_24dp.xml │ │ │ ├── ic_sticker_text_white_24dp.xml │ │ │ ├── ic_sticker_white_24dp.xml │ │ │ ├── ic_text_white_24dp.xml │ │ │ ├── ic_undo_white_24dp.xml │ │ │ ├── shape_delete_bg.xml │ │ │ ├── shape_redo_bg.xml │ │ │ └── shape_undo_bg.xml │ │ │ ├── layout │ │ │ └── activity_main.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── ic_launcher_background.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── outsbook │ │ └── examplejava │ │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── example-kotlin ├── .gitignore ├── app │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── outsbook │ │ │ └── examplekotlin │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── outsbook │ │ │ │ └── examplekotlin │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── drawable │ │ │ ├── app_icon.png │ │ │ ├── ic_cancel_yellow_24dp.xml │ │ │ ├── ic_color_black_24dp.xml │ │ │ ├── ic_color_yellow_24dp.xml │ │ │ ├── ic_delete_white_24dp.xml │ │ │ ├── ic_download_gray_24dp.xml │ │ │ ├── ic_launcher_background.xml │ │ │ ├── ic_minus_black_24dp.xml │ │ │ ├── ic_minus_yellow_24dp.xml │ │ │ ├── ic_panorama_240dp.xml │ │ │ ├── ic_plus_black_24dp.xml │ │ │ ├── ic_plus_yellow_24dp.xml │ │ │ ├── ic_redo_white_24dp.xml │ │ │ ├── ic_sticker_text_white_24dp.xml │ │ │ ├── ic_sticker_white_24dp.xml │ │ │ ├── ic_text_white_24dp.xml │ │ │ ├── ic_undo_white_24dp.xml │ │ │ ├── shape_delete_bg.xml │ │ │ ├── shape_redo_bg.xml │ │ │ └── shape_undo_bg.xml │ │ │ ├── layout │ │ │ └── activity_main.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── ic_launcher_background.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── outsbook │ │ └── examplekotlin │ │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle └── screenshot ├── screenshot_1.png ├── screenshot_2.png ├── screenshot_3.png ├── screenshot_4.png ├── screenshot_5.png ├── screenshot_6.png └── screenshot_7.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Gradle 6 | # ------ 7 | .gradle 8 | /build 9 | /buildSrc/build 10 | /buildSrc/subprojects/*/build 11 | /subprojects/*/build 12 | /subprojects/docs/src/samples/*/*/build 13 | /subprojects/internal-android-performance-testing/build-android-libs 14 | 15 | # IDEA 16 | # ---- 17 | .idea 18 | .shelf 19 | /*.iml 20 | /*.ipr 21 | /*.iws 22 | /buildSrc/.idea 23 | /buildSrc/.shelf 24 | /buildSrc/*.iml 25 | /buildSrc/*.ipr 26 | /buildSrc/*.iws 27 | /buildSrc/out 28 | /buildSrc/subprojects/*/*.iml 29 | /buildSrc/subprojects/*/out 30 | /out 31 | /subprojects/*/*.iml 32 | /subprojects/*/out 33 | /.teamcity/*.iml 34 | /.teamcity/target 35 | 36 | # Eclipse 37 | # ------- 38 | *.classpath 39 | *.project 40 | *.settings 41 | /bin 42 | /subprojects/*/bin 43 | atlassian-ide-plugin.xml 44 | .metadata/ 45 | 46 | # NetBeans 47 | # -------- 48 | .nb-gradle 49 | .nb-gradle-properties 50 | 51 | # Vim 52 | # --- 53 | *.sw[op] 54 | 55 | # Emacs 56 | # ----- 57 | *~ 58 | \#*\# 59 | .\#* 60 | 61 | # Textmate 62 | # -------- 63 | .textmate 64 | 65 | # Sublime Text 66 | # ------------ 67 | *.sublime-* 68 | 69 | # jEnv 70 | # ---- 71 | .java-version 72 | 73 | # macOS 74 | # ---- 75 | .DS_Store 76 | 77 | # HPROF 78 | # ----- 79 | *.hprof 80 | 81 | # Work dirs 82 | # --------- 83 | /incoming-distributions 84 | /intTestHomeDir 85 | 86 | # Logs 87 | # ---- 88 | /*.log 89 | 90 | # oh-my-zsh gradle plugin 91 | .gradletasknamecache 92 | 93 | # Files for the ART/Dalvik VM 94 | *.dex 95 | 96 | # Java class files 97 | *.class 98 | 99 | # Generated files 100 | bin/ 101 | gen/ 102 | out/ 103 | 104 | # Gradle files 105 | .gradle/ 106 | build/ 107 | 108 | # Local configuration file (sdk path, etc) 109 | local.properties 110 | 111 | # Proguard folder generated by Eclipse 112 | proguard/ 113 | 114 | # Log Files 115 | *.log 116 | 117 | # Android Studio Navigation editor temp files 118 | .navigation/ 119 | 120 | # Android Studio captures folder 121 | captures/ 122 | 123 | # Intellij 124 | *.iml 125 | .idea/workspace.xml 126 | .idea/tasks.xml 127 | .idea/gradle.xml 128 | .idea/dictionaries 129 | .idea/libraries 130 | 131 | # Keystore files 132 | *.jks 133 | 134 | # External native build folder generated in Android Studio 2.2 and later 135 | .externalNativeBuild 136 | 137 | # Google Services (e.g. APIs or Firebase) 138 | google-services.json 139 | 140 | # Freeline 141 | freeline.py 142 | freeline/ 143 | freeline_project_description.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright(c) 2020, Shahin ShamS 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CanvasEditor 2 | A Canvas/Image Editor library with easy support for canvas/image editing using paints, drawable sticker, and text sticker in Android. The lib source code writeen using Kotlin language. 3 | 4 | ## Index 5 | | Title | Description/Methods | 6 | | ------------ | ------------ | 7 | | [**Getting Started**](#getting-started) | Install the canvas editor library to your project | 8 | | [**Setup the Canvas Editor**](#setup-the-canvas-editor) | Setup the canvas editor to your project activity and activity layout | 9 | | [**Drawing**](#drawing) | [1. setPaintColor(color: Int)](#1-setpaintcolorcolor-int)
[2. setStrokeWidth(strokeWidth: Float)](#2-setstrokewidthstrokeWidth-float) | 10 | | [**Drawable/Bitmap Sticker**](#drawablebitmap-sticker) | [1. addDrawableSticker(drawable: Drawable)](#1-adddrawablestickerdrawable-drawable)
[2. addBitmapSticker(bitmap: Bitmap)](#2-addbitmapstickerbitmap-bitmap)| 11 | | [**Text Sticker**](#text-sticker) | [1. addTextSticker(text: String, textColor: Int, typeface: Typeface?)](#1-addtextStickertext-string-textcolor-int-typeface-typeface)
[2. addDrawableTextSticker(drawable: Drawable, text: String, textColor: Int, typeface: Typeface?)](#2-adddrawabletextstickerdrawable-drawable-text-string-textColor-int-typeface-typeface) | 12 | | [**Active Sticker Methods**](#active-sticker-methods) | [1. removeActiveSticker()](#1-removeactivesticker)
[2. doneActiveSticker()](#2-doneactivesticker)
[3. flipActiveSticker()](#3-flipactivesticker)
[4. zoomAndRotateActiveSticker(motionEvent: MotionEvent)](#4-zoomandrotateactivestickermotionevent-motionevent) | 13 | | [**Canvas Editor Methods**](#canvas-editor-methods) | [1. undo()](#1-undo)
[2. redo()](#2-redo)
[3. removeAll()](#3-removeall)
[4. downloadBitmap(): Bitmap](#4-downloadbitmap-bitmap) | 14 | | [**Canvas Editor Callback**](#canvas-editor-callback) | Set the listener for access callback functions | 15 | 16 | ## Getting Started 17 | To include the library in your project just simply add the dependencies. Choose one from Gradle, and Maven 18 | #### Gradle 19 | ```groovy 20 | implementation 'com.outsbook.libs:canvaseditor:1.0.0' 21 | ``` 22 | #### Maven 23 | ```xml 24 | 25 | com.outsbook.libs 26 | canvaseditor 27 | 1.0.0 28 | pom 29 | 30 | ``` 31 | 32 | ## Setup the Canvas Editor 33 | #### Add the `CanvasEditorView` to your Activity/Fragment layout 34 | ```xml 35 | 39 | ``` 40 | #### Get the `CanvasEditor` in your Activity 41 | ##### Kotlin 42 | ```kotlin 43 | import com.outsbook.libs.canvaseditor.CanvasEditorView 44 | 45 | class MainActivity : AppCompatActivity() { 46 | private lateinit var canvasEditor: CanvasEditorView 47 | 48 | override fun onCreate(savedInstanceState: Bundle?) { 49 | super.onCreate(savedInstanceState) 50 | setContentView(R.layout.activity_main) 51 | canvasEditor = findViewById(R.id.canvasEditor) 52 | } 53 | } 54 | ``` 55 | ##### Java 56 | ```java 57 | import com.outsbook.libs.canvaseditor.CanvasEditorView; 58 | 59 | public class MainActivity extends AppCompatActivity { 60 | private CanvasEditorView canvasEditor; 61 | 62 | @Override 63 | protected void onCreate(Bundle savedInstanceState) { 64 | super.onCreate(savedInstanceState); 65 | setContentView(R.layout.activity_main); 66 | canvasEditor = findViewById(R.id.canvasEditor); 67 | } 68 | } 69 | ``` 70 | ##### Preview 71 | ![](https://github.com/outsbook/CanvasEditor/blob/master/screenshot/screenshot_1.png?raw=true) 72 | 73 | Now you are ready to play with `CanvasEditor` 74 | 75 | ## Drawing 76 | | # | Method | Action | 77 | | ------------ | ------------ | ------------ | 78 | | 1 | [setPaintColor(color: Int)](#1-setpaintcolorcolor-int) | Set the brush color to paint | 79 | | 2 | [setStrokeWidth(strokeWidth: Float)](#2-setstrokewidthstrokeWidth-float) | Set the brush stroke width to paint | 80 | #### 1. setPaintColor(color: Int) 81 | ##### Kotlin 82 | ```kotlin 83 | val color = ContextCompat.getColor(this, R.color.colorBlack) 84 | canvasEditor.setPaintColor(color) 85 | ``` 86 | ##### Java 87 | ```java 88 | int color = ContextCompat.getColor(this, R.color.colorBlack); 89 | canvasEditor.setPaintColor(color); 90 | ``` 91 | ##### Preview 92 | ![](https://github.com/outsbook/CanvasEditor/blob/master/screenshot/screenshot_2.png?raw=true) 93 | 94 | #### 2. setStrokeWidth(strokeWidth: Float) 95 | ##### Kotlin 96 | ```kotlin 97 | val strokeWidth = 20f 98 | canvasEditor.setStrokeWidth(strokeWidth) 99 | ``` 100 | ##### Java 101 | ```java 102 | float strokeWidth = 20f; 103 | canvasEditor.setStrokeWidth(strokeWidth); 104 | ``` 105 | ##### Preview 106 | ![](https://github.com/outsbook/CanvasEditor/blob/master/screenshot/screenshot_3.png?raw=true) 107 | 108 | ## Drawable/Bitmap Sticker 109 | | # | Method | Action | 110 | | ------------ | ------------ | ------------ | 111 | | 1 | [addDrawableSticker(drawable: Drawable)](#1-adddrawablestickerdrawable-drawable) | Add drawable sticker to the canvas editor | 112 | | 2 | [addBitmapSticker(bitmap: Bitmap)](#2-addbitmapstickerbitmap-bitmap) | Add bitmap sticker to the canvas editor | 113 | #### 1. addDrawableSticker(drawable: Drawable) 114 | ##### Kotlin 115 | ```kotlin 116 | val drawable = ContextCompat.getDrawable(this, R.drawable.app_icon) 117 | drawable?.let{ 118 | canvasEditor.addDrawableSticker(drawable) 119 | } 120 | ``` 121 | ##### Java 122 | ```java 123 | Drawable drawable = ContextCompat.getDrawable(this, R.drawable.app_icon); 124 | if(drawable != null){ 125 | canvasEditor.addDrawableSticker(drawable); 126 | } 127 | ``` 128 | ##### Preview 129 | ![](https://github.com/outsbook/CanvasEditor/blob/master/screenshot/screenshot_4.png?raw=true) 130 | 131 | #### 2. addBitmapSticker(bitmap: Bitmap) 132 | 133 | ##### Kotlin 134 | ```kotlin 135 | val bitmap = //get your bitmap 136 | bitmap?.let{ 137 | canvasEditor.addBitmapSticker(bitmap) 138 | } 139 | ``` 140 | ##### Java 141 | ```java 142 | Bitmap bitmap = //get your bitmap 143 | if(bitmap != null){ 144 | canvasEditor.addBitmapSticker(drawable); 145 | } 146 | ``` 147 | ##### Preview 148 | ![](https://github.com/outsbook/CanvasEditor/blob/master/screenshot/screenshot_5.png?raw=true) 149 | 150 | ## Text Sticker 151 | | # | Method | Action | 152 | | ------------ | ------------ | ------------ | 153 | | 1 | [addTextSticker(text: String, textColor: Int, typeface: Typeface?)](#1-addtextStickertext-string-textcolor-int-typeface-typeface) | Add text sticker to the canvas editor | 154 | | 2 | [addDrawableTextSticker(drawable: Drawable, text: String, textColor: Int, typeface: Typeface?)](#2-adddrawabletextstickerdrawable-drawable-text-string-textColor-int-typeface-typeface) | Add text sticker with drawable background to the canvas editor | 155 | #### 1. addTextSticker(text: String, textColor: Int, typeface: Typeface?) 156 | ##### Kotlin 157 | ```kotlin 158 | val text = "Canvas" 159 | val textColor = ContextCompat.getColor(this, R.color.colorPrimary) 160 | canvasEditor.addTextSticker(text, textColor, null) 161 | ``` 162 | ##### Java 163 | ```java 164 | String text = "Canvas"; 165 | int color = ContextCompat.getColor(this, R.color.colorPrimary); 166 | canvasEditor.addTextSticker(text, color, null); 167 | ``` 168 | ##### Preview 169 | ![](https://github.com/outsbook/CanvasEditor/blob/master/screenshot/screenshot_6.png?raw=true) 170 | 171 | #### 2. addDrawableTextSticker(drawable: Drawable, text: String, textColor: Int, typeface: Typeface?) 172 | ##### Kotlin 173 | ```kotlin 174 | val drawable = ContextCompat.getDrawable(this, R.drawable.ic_panorama_240dp) 175 | val text = "Canvas" 176 | val textColor = ContextCompat.getColor(this, R.color.colorYellow) 177 | drawable?.let{ 178 | canvasEditor.addDrawableTextSticker(it, text, textColor, null) 179 | } 180 | ``` 181 | ##### Java 182 | ```java 183 | Drawable drawable = ContextCompat.getDrawable(this, R.drawable.ic_panorama_240dp); 184 | String text = "Canvas"; 185 | int textColor = ContextCompat.getColor(this, R.color.colorYellow); 186 | if(drawable != null){ 187 | canvasEditor.addDrawableTextSticker(drawable, text, textColor, null); 188 | } 189 | ``` 190 | ##### Preview 191 | ![](https://github.com/outsbook/CanvasEditor/blob/master/screenshot/screenshot_7.png?raw=true) 192 | 193 | ## Active Sticker Methods 194 | | # | Method | Action | 195 | | ------------ | ------------ | ------------ | 196 | | 1 | [removeActiveSticker()](#1-removeactivesticker) | Remove active sticker from canvas editor | 197 | | 2 | [doneActiveSticker()](#2-doneactivesticker) | Editing done of active sticker on canvas editor | 198 | | 3 | [flipActiveSticker()](#3-flipactivesticker) | Flip active sticker on canvas editor | 199 | | 4 | [zoomAndRotateActiveSticker(motionEvent: MotionEvent)](#4-zoomandrotateactivestickermotionevent-motionevent) | Zoom and rotate active sticker with motihn event on canvas editor| 200 | #### 1. removeActiveSticker() 201 | ##### Kotlin 202 | ```kotlin 203 | canvasEditor.removeActiveSticker() 204 | ``` 205 | ##### Java 206 | ```java 207 | canvasEditor.removeActiveSticker(); 208 | ``` 209 | #### 2. doneActiveSticker() 210 | ##### Kotlin 211 | ```kotlin 212 | canvasEditor.doneActiveSticker() 213 | ``` 214 | ##### Java 215 | ```java 216 | canvasEditor.doneActiveSticker(); 217 | ``` 218 | #### 3. flipActiveSticker() 219 | ##### Kotlin 220 | ```kotlin 221 | canvasEditor.flipActiveSticker() 222 | ``` 223 | ##### Java 224 | ```java 225 | canvasEditor.flipActiveSticker(); 226 | ``` 227 | #### 4. zoomAndRotateActiveSticker(motionEvent: MotionEvent) 228 | ##### Kotlin 229 | ```kotlin 230 | val motionEvent = //Set your motion event 231 | canvasEditor.zoomAndRotateActiveSticker(motionEvent) 232 | ``` 233 | ##### Java 234 | ```java 235 | MotionEvent motionEvent = //Set your motion event 236 | canvasEditor.zoomAndRotateActiveSticker(motionEvent); 237 | ``` 238 | ## Canvas Editor Methods 239 | | # | Method | Action | 240 | | ------------ | ------------ | ------------ | 241 | | 1 | [undo()](#1-undo) | Undo from canvas editor | 242 | | 2 | [redo()](#2-redo) | Redo to canvas editor | 243 | | 3 | [removeAll()](#3-removeall) | Delete everything from canvas editor | 244 | | 4 | [downloadBitmap(): Bitmap](#4-downloadbitmap-bitmap) | Get the canvas as bitmap, you can play with the bitmap :)| 245 | #### 1. undo() 246 | ##### Kotlin 247 | ```kotlin 248 | canvasEditor.undo() 249 | ``` 250 | ##### Java 251 | ```java 252 | canvasEditor.undo(); 253 | ``` 254 | #### 2. redo() 255 | ##### Kotlin 256 | ```kotlin 257 | canvasEditor.redo() 258 | ``` 259 | ##### Java 260 | ```java 261 | canvasEditor.redo(); 262 | ``` 263 | #### 3. removeAll() 264 | ##### Kotlin 265 | ```kotlin 266 | canvasEditor.removeAll() 267 | ``` 268 | ##### Java 269 | ```java 270 | canvasEditor.removeAll(); 271 | ``` 272 | #### 4. downloadBitmap(): Bitmap 273 | ##### Kotlin 274 | ```kotlin 275 | val bitmap = canvasEditor.downloadBitmap() 276 | ``` 277 | ##### Java 278 | ```java 279 | Bitmap bitmap = canvasEditor.downloadBitmap(); 280 | ``` 281 | 282 | ## Canvas Editor Callback 283 | ##### Kotlin 284 | ```kotlin 285 | import com.outsbook.libs.canvaseditor.CanvasEditorView 286 | import com.outsbook.libs.canvaseditor.listeners.CanvasEditorListener 287 | 288 | class MainActivity : AppCompatActivity() { 289 | private lateinit var canvasEditor: CanvasEditorView 290 | 291 | override fun onCreate(savedInstanceState: Bundle?) { 292 | super.onCreate(savedInstanceState) 293 | setContentView(R.layout.activity_main) 294 | canvasEditor = findViewById(R.id.canvasEditor) 295 | 296 | canvasEditor.setListener(object: CanvasEditorListener { 297 | override fun onEnableUndo(isEnable: Boolean) { 298 | // isEnable = true (undo list is not empty) 299 | // isEnable = false (undo list is empty) 300 | buttonUndo.imageAlpha = if(isEnable) 255 else 50 301 | } 302 | 303 | override fun onEnableRedo(isEnable: Boolean) { 304 | // isEnable = true (redo list is not empty) 305 | // isEnable = false (redo list is empty) 306 | buttonRedo.imageAlpha = if(isEnable) 255 else 50 307 | } 308 | 309 | override fun onTouchEvent(event: MotionEvent) { 310 | //When the canvas touch 311 | } 312 | 313 | override fun onStickerActive() { 314 | //When a sticker change to active mode 315 | } 316 | 317 | override fun onStickerRemove() { 318 | //When a sticker remove from canvas 319 | } 320 | 321 | override fun onStickerDone() { 322 | //When the active sticker added to canvas 323 | } 324 | 325 | override fun onStickerZoomAndRotate() { 326 | //When the active sticker zoom or rotate 327 | } 328 | 329 | override fun onStickerFlip() { 330 | //When the active sticker flip 331 | } 332 | }) 333 | } 334 | } 335 | ``` 336 | ##### Java 337 | ```java 338 | import com.outsbook.libs.canvaseditor.CanvasEditorView; 339 | import com.outsbook.libs.canvaseditor.listeners.CanvasEditorListener; 340 | 341 | public class MainActivity extends AppCompatActivity { 342 | private CanvasEditorView canvasEditor; 343 | 344 | @Override 345 | protected void onCreate(Bundle savedInstanceState) { 346 | super.onCreate(savedInstanceState); 347 | setContentView(R.layout.activity_main); 348 | canvasEditor = findViewById(R.id.canvasEditor); 349 | 350 | canvasEditor.setListener(new CanvasEditorListener() { 351 | @Override 352 | public void onEnableUndo(boolean isEnable) { 353 | // isEnable = true (undo list is not empty) 354 | // isEnable = false (undo list is empty) 355 | } 356 | 357 | @Override 358 | public void onEnableRedo(boolean isEnable) { 359 | // isEnable = true (redo list is not empty) 360 | // isEnable = false (redo list is empty) 361 | } 362 | 363 | @Override 364 | public void onTouchEvent(MotionEvent motionEvent) { 365 | //When the canvas touch 366 | } 367 | 368 | @Override 369 | public void onStickerActive() { 370 | //When a sticker change to active mode 371 | } 372 | 373 | @Override 374 | public void onStickerRemove() { 375 | //When a sticker remove from canvas 376 | } 377 | 378 | @Override 379 | public void onStickerDone() { 380 | //When the active sticker added to canvas 381 | } 382 | 383 | @Override 384 | public void onStickerZoomAndRotate() { 385 | //When the active sticker zoom or rotate 386 | } 387 | 388 | @Override 389 | public void onStickerFlip() { 390 | //When the active sticker flip 391 | } 392 | }); 393 | } 394 | } 395 | ``` 396 | 397 | ## How to contribute? 398 | - Create an issue first to discuss about the changes you are suggesting. 399 | - Fork the project. 400 | - Create a branch with name CE-[Issue Number] Example: CE-123 401 | - Make required changes and commit to that branch. 402 | - Generate pull request. Mention all the required description regarding changes you made. 403 | 404 | ## Maintained by 405 | Shahin ShamS (GitHub: [outsbook](https://github.com/outsbook/)) 406 | 407 | ## Licenses 408 | Copyright(c) 2020, Shahin ShamS 409 | 410 | Licensed under the Apache License, Version 2.0 (the "License"); 411 | you may not use this file except in compliance with the License. 412 | You may obtain a copy of the License at 413 | 414 | http://www.apache.org/licenses/LICENSE-2.0 415 | 416 | Unless required by applicable law or agreed to in writing, software 417 | distributed under the License is distributed on an "AS IS" BASIS, 418 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 419 | See the License for the specific language governing permissions and 420 | limitations under the License. 421 | -------------------------------------------------------------------------------- /canvaseditor/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /canvaseditor/bintray.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.jfrog.bintray' 2 | 3 | version = libraryVersion 4 | 5 | if (project.hasProperty("android")) { // Android libraries 6 | task sourcesJar(type: Jar) { 7 | classifier = 'sources' 8 | from android.sourceSets.main.java.srcDirs 9 | } 10 | 11 | task javadoc(type: Javadoc) { 12 | source = android.sourceSets.main.java.srcDirs 13 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 14 | } 15 | } else { // Java libraries 16 | task sourcesJar(type: Jar, dependsOn: classes) { 17 | classifier = 'sources' 18 | from sourceSets.main.allSource 19 | } 20 | } 21 | 22 | task javadocJar(type: Jar, dependsOn: javadoc) { 23 | classifier = 'javadoc' 24 | from javadoc.destinationDir 25 | } 26 | 27 | artifacts { 28 | archives javadocJar 29 | archives sourcesJar 30 | } 31 | 32 | // Bintray 33 | Properties properties = new Properties() 34 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 35 | 36 | bintray { 37 | user = properties.getProperty("bintray.user") 38 | key = properties.getProperty("bintray.apikey") 39 | 40 | configurations = ['archives'] 41 | pkg { 42 | repo = bintrayRepo 43 | name = bintrayName 44 | desc = libraryDescription 45 | websiteUrl = siteUrl 46 | vcsUrl = gitUrl 47 | licenses = allLicenses 48 | publish = true 49 | publicDownloadNumbers = true 50 | version { 51 | desc = libraryDescription 52 | gpg { 53 | sign = true //Determines whether to GPG sign the files. The default is false 54 | passphrase = properties.getProperty("bintray.gpg.password") 55 | //Optional. The passphrase for GPG signing' 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /canvaseditor/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 29 7 | buildToolsVersion "29.0.2" 8 | 9 | defaultConfig { 10 | minSdkVersion 16 11 | targetSdkVersion 29 12 | versionCode 1 13 | versionName "1.0.0" 14 | 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | consumerProguardFiles 'consumer-rules.pro' 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | 26 | tasks.withType(Javadoc).all { enabled = false } 27 | } 28 | 29 | dependencies { 30 | implementation fileTree(dir: 'libs', include: ['*.jar']) 31 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 32 | implementation 'androidx.appcompat:appcompat:1.1.0' 33 | implementation 'androidx.core:core-ktx:1.2.0' 34 | testImplementation 'junit:junit:4.13' 35 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 36 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 37 | } 38 | 39 | ext { 40 | bintrayRepo = 'CanvasEditor' // Repo name in bintray dashboard 41 | bintrayName = 'com.outsbook.libs.canvaseditor' // package name of the bintray repo 42 | 43 | publishedGroupId = 'com.outsbook.libs' // this is the ID we want to see in implementation line 44 | libraryName = 'canvaseditor' // this is the module name of library 45 | artifact = 'canvaseditor' // this is the artifact we want to see in implementation line 46 | 47 | libraryDescription = 'A Canvas/Image Editor library with easy support for canvas/image editing using paints, drawable sticker, and text sticker in Android (Kotlin).' // description of library 48 | 49 | siteUrl = 'https://github.com/outsbook/CanvasEditor' // git repo url 50 | gitUrl = 'https://github.com/outsbook/CanvasEditor.git' // git repo vcs url 51 | 52 | libraryVersion = '1.0.0' // library version 53 | 54 | developerId = 'outsbook' // This is your bintray username 55 | developerName = 'Shahin ShamS' // Developer's name 56 | developerEmail = 'shamscse@gmail.com' // Developer's email 57 | 58 | licenseName = 'The Apache Software License, Version 2.0' // for example, The Apache Software License, Version 2.0 59 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' // for example, http://www.apache.org/licenses/LICENSE-2.0.txt 60 | allLicenses = ["Apache-2.0"] // array of licenses, for example, ["Apache-2.0"] 61 | } 62 | 63 | apply from: 'install.gradle' 64 | apply from: 'bintray.gradle' 65 | 66 | //commands 67 | // ./gradlew install 68 | // ./gradlew bintrayUpload 69 | -------------------------------------------------------------------------------- /canvaseditor/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/canvaseditor/consumer-rules.pro -------------------------------------------------------------------------------- /canvaseditor/install.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.github.dcendents.android-maven' 2 | 3 | group = publishedGroupId // Maven Group ID for the artifact 4 | 5 | install { 6 | repositories.mavenInstaller { 7 | // This generates POM.xml with proper parameters 8 | pom { 9 | project { 10 | packaging 'aar' 11 | groupId publishedGroupId 12 | artifactId artifact 13 | 14 | // Add your description here 15 | name libraryName 16 | description libraryDescription 17 | url siteUrl 18 | 19 | // Set your license 20 | licenses { 21 | license { 22 | name licenseName 23 | url licenseUrl 24 | } 25 | } 26 | developers { 27 | developer { 28 | id developerId 29 | name developerName 30 | email developerEmail 31 | } 32 | } 33 | scm { 34 | connection gitUrl 35 | developerConnection gitUrl 36 | url siteUrl 37 | 38 | } 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /canvaseditor/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 | -------------------------------------------------------------------------------- /canvaseditor/src/androidTest/java/com/outsbook/libs/canvaseditor/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.outsbook.libs.canvaseditor 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.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.getInstrumentation().targetContext 22 | assertEquals("com.outsbook.libs.canvaseditor.test", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /canvaseditor/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /canvaseditor/src/main/java/com/outsbook/libs/canvaseditor/CanvasEditorView.kt: -------------------------------------------------------------------------------- 1 | package com.outsbook.libs.canvaseditor 2 | 3 | import android.annotation.TargetApi 4 | import android.content.Context 5 | import android.graphics.Bitmap 6 | import android.graphics.Typeface 7 | import android.graphics.drawable.Drawable 8 | import android.os.Build 9 | import android.util.AttributeSet 10 | import android.util.Log 11 | import android.view.GestureDetector 12 | import android.view.MotionEvent 13 | import android.view.View 14 | import android.view.ViewGroup 15 | import android.widget.RelativeLayout 16 | import androidx.core.content.ContextCompat 17 | import com.outsbook.libs.canvaseditor.enums.DrawType 18 | import com.outsbook.libs.canvaseditor.listeners.PaintViewListener 19 | import com.outsbook.libs.canvaseditor.listeners.CanvasEditorListener 20 | import com.outsbook.libs.canvaseditor.listeners.StickerViewListener 21 | import com.outsbook.libs.canvaseditor.models.DrawObject 22 | import com.outsbook.libs.canvaseditor.paints.PaintView 23 | import com.outsbook.libs.canvaseditor.stickers.BitmapSticker 24 | import com.outsbook.libs.canvaseditor.stickers.DrawableSticker 25 | import com.outsbook.libs.canvaseditor.stickers.StickerView 26 | import com.outsbook.libs.canvaseditor.stickers.TextSticker 27 | 28 | class CanvasEditorView : RelativeLayout{ 29 | @JvmOverloads 30 | constructor( 31 | context: Context, 32 | attrs: AttributeSet? = null, 33 | defStyleAttr: Int = 0 34 | ) : super(context, attrs, defStyleAttr) 35 | 36 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 37 | constructor( 38 | context: Context, 39 | attrs: AttributeSet?, 40 | defStyleAttr: Int, 41 | defStyleRes: Int 42 | ) : super(context, attrs, defStyleAttr, defStyleRes) 43 | 44 | private val mUndoList = mutableListOf() 45 | private val mRedoList = mutableListOf() 46 | 47 | private val paintViewListener = object : PaintViewListener { 48 | override fun onTouchUp(obj: DrawObject) { 49 | mUndoList.add(obj) 50 | mRedoList.clear() 51 | mListener?.onEnableUndo(true) 52 | mListener?.onEnableRedo(false) 53 | } 54 | 55 | override fun onClick(x: Float, y: Float) { 56 | val pos = findTapedSticker(x, y) 57 | if(pos > -1) 58 | enableEditModeSticker(pos) 59 | } 60 | 61 | override fun onTouchEvent(event: MotionEvent) { 62 | mListener?.onTouchEvent(event) 63 | } 64 | } 65 | 66 | private val stickerViewListener = object : StickerViewListener { 67 | override fun onRemove() { 68 | mListener?.onStickerRemove() 69 | mListener?.onEnableUndo(mUndoList.isNotEmpty()) 70 | } 71 | override fun onDone(obj: DrawObject) { 72 | addStickerToPaint(obj) 73 | mListener?.onStickerDone() 74 | } 75 | override fun onZoomAndRotate() { 76 | mListener?.onStickerZoomAndRotate() 77 | } 78 | override fun onFlip() { 79 | mListener?.onStickerFlip() 80 | } 81 | 82 | override fun onClickStickerOutside(x: Float, y: Float) { 83 | val pos = findTapedSticker(x, y) 84 | if(pos > -1){ 85 | enableEditModeSticker(pos) 86 | } 87 | } 88 | 89 | override fun onTouchEvent(event: MotionEvent) { 90 | mListener?.onTouchEvent(event) 91 | } 92 | } 93 | 94 | private val mPaintView: PaintView = PaintView(context, paintViewListener) 95 | private val mStickerView: StickerView = StickerView(context, stickerViewListener) 96 | private var mListener: CanvasEditorListener? = null 97 | 98 | init { 99 | val params = LayoutParams( 100 | ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT 101 | ) 102 | mPaintView.layoutParams = params 103 | mPaintView.setBackgroundColor(ContextCompat.getColor(context, android.R.color.white)) 104 | addView(mPaintView) 105 | 106 | mStickerView.layoutParams = params 107 | mStickerView.setBackgroundColor( 108 | ContextCompat.getColor( 109 | context, 110 | android.R.color.transparent 111 | ) 112 | ) 113 | addView(mStickerView) 114 | mStickerView.visibility = View.GONE 115 | } 116 | 117 | fun setListener(listener: CanvasEditorListener) { 118 | mListener = listener 119 | } 120 | 121 | fun setPaintColor(color: Int) { 122 | doneStickerEdit() 123 | mPaintView.paint.color = color 124 | } 125 | 126 | fun setStrokeWidth(strokeWidth: Float) { 127 | doneStickerEdit() 128 | mPaintView.paint.strokeWidth = strokeWidth 129 | } 130 | 131 | fun setStrokeCap(strokeCap: Paint.Cap) { 132 | doneStickerEdit() 133 | mPaintView.paint.strokeCap = strokeCap 134 | } 135 | 136 | //region add sticker 137 | fun addDrawableSticker(drawable: Drawable) { 138 | doneStickerEdit() 139 | mStickerView.visibility = View.VISIBLE 140 | val sticker = DrawableSticker(drawable) 141 | mStickerView.addSticker(sticker) 142 | mListener?.onEnableUndo(true) 143 | mListener?.onEnableRedo(false) 144 | mListener?.onStickerActive() 145 | } 146 | 147 | fun addBitmapSticker(bitmap: Bitmap) { 148 | doneStickerEdit() 149 | mStickerView.visibility = View.VISIBLE 150 | val sticker = BitmapSticker(context, bitmap) 151 | mStickerView.addSticker(sticker) 152 | mListener?.onEnableUndo(true) 153 | mListener?.onEnableRedo(false) 154 | mListener?.onStickerActive() 155 | } 156 | 157 | fun addTextSticker(text: String, textColor: Int, typeface: Typeface?) { 158 | doneStickerEdit() 159 | mStickerView.visibility = View.VISIBLE 160 | val sticker = TextSticker(context, null) 161 | sticker.setText(text) 162 | sticker.setTextColor(textColor) 163 | typeface?.let { 164 | sticker.setTypeface(it) 165 | } 166 | sticker.setAlpha(255) 167 | sticker.resizeText() 168 | mStickerView.addSticker(sticker) 169 | mListener?.onEnableUndo(true) 170 | mListener?.onEnableRedo(false) 171 | mListener?.onStickerActive() 172 | } 173 | 174 | fun addDrawableTextSticker( 175 | drawable: Drawable, 176 | text: String, 177 | textColor: Int, 178 | typeface: Typeface? 179 | ) { 180 | doneStickerEdit() 181 | mStickerView.visibility = View.VISIBLE 182 | val sticker = TextSticker(context, drawable) 183 | sticker.setText(text) 184 | sticker.setTextColor(textColor) 185 | typeface?.let { 186 | sticker.setTypeface(it) 187 | } 188 | sticker.resizeText() 189 | mStickerView.addSticker(sticker) 190 | mListener?.onEnableUndo(true) 191 | mListener?.onEnableRedo(false) 192 | mListener?.onStickerActive() 193 | } 194 | 195 | fun doneActiveSticker(){ 196 | if (mStickerView.visibility == View.VISIBLE) { 197 | mStickerView.done() 198 | } 199 | } 200 | 201 | fun removeActiveSticker(){ 202 | if (mStickerView.visibility == View.VISIBLE) { 203 | mStickerView.remove() 204 | } 205 | } 206 | 207 | fun zoomAndRotateActiveSticker(motionEvent: MotionEvent){ 208 | if (mStickerView.visibility == View.VISIBLE) { 209 | mStickerView.zoomAndRotate(motionEvent) 210 | } 211 | } 212 | 213 | fun flipActiveSticker(){ 214 | if (mStickerView.visibility == View.VISIBLE) { 215 | mStickerView.flip() 216 | } 217 | } 218 | //endregion 219 | 220 | fun undo() { 221 | if (mStickerView.visibility == View.VISIBLE) { 222 | mStickerView.remove() 223 | return 224 | } 225 | if (mUndoList.isNotEmpty()) { 226 | mRedoList.add(mUndoList.last()) 227 | mUndoList.removeAt(mUndoList.lastIndex) 228 | mPaintView.initCanvas() 229 | mUndoList.forEach { 230 | drawObject(it) 231 | } 232 | mListener?.onEnableUndo(mUndoList.isNotEmpty()) 233 | mListener?.onEnableRedo(mRedoList.isNotEmpty()) 234 | } 235 | } 236 | 237 | fun redo() { 238 | if (mRedoList.isNotEmpty()) { 239 | val obj = mRedoList.last() 240 | mUndoList.add(obj) 241 | mRedoList.removeAt(mRedoList.lastIndex) 242 | drawObject(obj) 243 | mListener?.onEnableUndo(mUndoList.isNotEmpty()) 244 | mListener?.onEnableRedo(mRedoList.isNotEmpty()) 245 | } 246 | } 247 | 248 | fun removeAll(){ 249 | mUndoList.clear() 250 | mRedoList.clear() 251 | mStickerView.remove() 252 | mPaintView.initCanvas() 253 | mListener?.onEnableUndo(false) 254 | mListener?.onEnableRedo(false) 255 | } 256 | 257 | fun downloadBitmap(): Bitmap{ 258 | doneStickerEdit() 259 | return mPaintView.extraBitmap 260 | } 261 | 262 | private fun drawObject(obj: DrawObject) { 263 | when (obj.drawType) { 264 | DrawType.PATH -> { 265 | mPaintView.drawPath(obj.pathAndPaint!!) 266 | } 267 | DrawType.STICKER -> { 268 | mPaintView.drawSticker(obj.sticker!!) 269 | } 270 | } 271 | } 272 | 273 | //region find double tap inside sticker 274 | private fun findTapedSticker(x: Float, y: Float): Int { 275 | for (i in mUndoList.size - 1 downTo 0) { 276 | val obj = mUndoList[i] 277 | if (obj.drawType == DrawType.STICKER) { 278 | val sticker = obj.sticker!! 279 | if (sticker.contains(x, y)) { 280 | return i 281 | } 282 | } 283 | } 284 | return -1 285 | } 286 | 287 | private fun enableEditModeSticker(pos: Int) { 288 | val obj = mUndoList[pos] 289 | val sticker = obj.sticker!! 290 | mStickerView.visibility = View.VISIBLE 291 | mStickerView.currentSticker = sticker 292 | mUndoList.removeAt(pos) 293 | mPaintView.initCanvas() 294 | mUndoList.forEach { 295 | drawObject(it) 296 | } 297 | mRedoList.clear() 298 | mListener?.onEnableUndo(true) 299 | mListener?.onEnableRedo(mRedoList.isNotEmpty()) 300 | mListener?.onStickerActive() 301 | } 302 | 303 | private fun addStickerToPaint(obj: DrawObject) { 304 | mPaintView.drawSticker(obj.sticker!!) 305 | mUndoList.add(obj) 306 | mRedoList.clear() 307 | mListener?.onEnableUndo(true) 308 | mListener?.onEnableRedo(false) 309 | } 310 | 311 | private fun doneStickerEdit() { 312 | if (mStickerView.visibility == View.VISIBLE) { 313 | mStickerView.done() 314 | } 315 | } 316 | } 317 | //endregion -------------------------------------------------------------------------------- /canvaseditor/src/main/java/com/outsbook/libs/canvaseditor/constants/ActionMode.kt: -------------------------------------------------------------------------------- 1 | package com.outsbook.libs.canvaseditor.constants 2 | 3 | internal class ActionMode { 4 | companion object { 5 | var NONE = 0 6 | var DRAG = 1 7 | var ZOOM_WITH_TWO_FINGER = 2 8 | var ICON = 3 9 | var CLICK = 4 10 | } 11 | } -------------------------------------------------------------------------------- /canvaseditor/src/main/java/com/outsbook/libs/canvaseditor/constants/ConstantSticker.kt: -------------------------------------------------------------------------------- 1 | package com.outsbook.libs.canvaseditor.constants 2 | 3 | internal class ConstantSticker { 4 | companion object{ 5 | var CENTER = 1 6 | var TOP = 1 shl 1 7 | var LEFT = 1 shl 2 8 | var RIGHT = 1 shl 3 9 | var BOTTOM = 1 shl 4 10 | } 11 | } -------------------------------------------------------------------------------- /canvaseditor/src/main/java/com/outsbook/libs/canvaseditor/constants/ConstantStickerIcon.kt: -------------------------------------------------------------------------------- 1 | package com.outsbook.libs.canvaseditor.constants 2 | 3 | import android.content.res.Resources 4 | 5 | internal class ConstantStickerIcon { 6 | companion object{ 7 | val DEFAULT_ICON_RADIUS = 14f * Resources.getSystem().displayMetrics.density 8 | 9 | const val LEFT_TOP = 0 10 | const val RIGHT_TOP = 1 11 | const val LEFT_BOTTOM = 2 12 | const val RIGHT_BOTTOM = 3 13 | } 14 | } -------------------------------------------------------------------------------- /canvaseditor/src/main/java/com/outsbook/libs/canvaseditor/enums/DrawType.kt: -------------------------------------------------------------------------------- 1 | package com.outsbook.libs.canvaseditor.enums 2 | 3 | internal enum class DrawType { 4 | PATH, 5 | STICKER 6 | } -------------------------------------------------------------------------------- /canvaseditor/src/main/java/com/outsbook/libs/canvaseditor/events/DeleteIconEvent.kt: -------------------------------------------------------------------------------- 1 | package com.outsbook.libs.canvaseditor.events 2 | 3 | import android.view.MotionEvent 4 | import com.outsbook.libs.canvaseditor.listeners.StickerIconListener 5 | import com.outsbook.libs.canvaseditor.stickers.StickerView 6 | 7 | internal class DeleteIconEvent: StickerIconListener { 8 | override fun onActionDown(stickerView: StickerView?, event: MotionEvent?) {} 9 | override fun onActionMove(stickerView: StickerView, event: MotionEvent) {} 10 | override fun onActionUp(stickerView: StickerView, event: MotionEvent?) { 11 | stickerView.remove() 12 | } 13 | } -------------------------------------------------------------------------------- /canvaseditor/src/main/java/com/outsbook/libs/canvaseditor/events/DoneIconEvent.kt: -------------------------------------------------------------------------------- 1 | package com.outsbook.libs.canvaseditor.events 2 | 3 | import android.view.MotionEvent 4 | import com.outsbook.libs.canvaseditor.listeners.StickerIconListener 5 | import com.outsbook.libs.canvaseditor.stickers.StickerView 6 | 7 | internal class DoneIconEvent: StickerIconListener { 8 | override fun onActionDown(stickerView: StickerView?, event: MotionEvent?) {} 9 | override fun onActionMove(stickerView: StickerView, event: MotionEvent) {} 10 | override fun onActionUp(stickerView: StickerView, event: MotionEvent?) { 11 | stickerView.done() 12 | } 13 | } -------------------------------------------------------------------------------- /canvaseditor/src/main/java/com/outsbook/libs/canvaseditor/events/FlipIconEvent.kt: -------------------------------------------------------------------------------- 1 | package com.outsbook.libs.canvaseditor.events 2 | 3 | import android.view.MotionEvent 4 | import com.outsbook.libs.canvaseditor.listeners.StickerIconListener 5 | import com.outsbook.libs.canvaseditor.stickers.StickerView 6 | 7 | internal class FlipIconEvent: StickerIconListener { 8 | override fun onActionDown(stickerView: StickerView?, event: MotionEvent?) {} 9 | override fun onActionMove(stickerView: StickerView, event: MotionEvent) {} 10 | override fun onActionUp(stickerView: StickerView, event: MotionEvent?) { 11 | stickerView.flip() 12 | } 13 | } -------------------------------------------------------------------------------- /canvaseditor/src/main/java/com/outsbook/libs/canvaseditor/events/ZoomIconEvent.kt: -------------------------------------------------------------------------------- 1 | package com.outsbook.libs.canvaseditor.events 2 | 3 | import android.view.MotionEvent 4 | import com.outsbook.libs.canvaseditor.listeners.StickerIconListener 5 | import com.outsbook.libs.canvaseditor.stickers.StickerView 6 | 7 | internal class ZoomIconEvent: StickerIconListener { 8 | override fun onActionDown(stickerView: StickerView?, event: MotionEvent?) {} 9 | override fun onActionMove(stickerView: StickerView, event: MotionEvent) { 10 | stickerView.zoomAndRotate(event) 11 | } 12 | override fun onActionUp(stickerView: StickerView, event: MotionEvent?) {} 13 | } -------------------------------------------------------------------------------- /canvaseditor/src/main/java/com/outsbook/libs/canvaseditor/listeners/CanvasEditorListener.kt: -------------------------------------------------------------------------------- 1 | package com.outsbook.libs.canvaseditor.listeners 2 | 3 | import android.view.MotionEvent 4 | 5 | interface CanvasEditorListener { 6 | fun onEnableUndo(isEnable: Boolean) 7 | fun onEnableRedo(isEnable: Boolean) 8 | fun onTouchEvent(event: MotionEvent) {} 9 | 10 | fun onStickerActive() {} 11 | fun onStickerRemove() {} 12 | fun onStickerDone() {} 13 | fun onStickerZoomAndRotate() {} 14 | fun onStickerFlip() {} 15 | } -------------------------------------------------------------------------------- /canvaseditor/src/main/java/com/outsbook/libs/canvaseditor/listeners/PaintViewListener.kt: -------------------------------------------------------------------------------- 1 | package com.outsbook.libs.canvaseditor.listeners 2 | 3 | import android.view.MotionEvent 4 | import com.outsbook.libs.canvaseditor.models.DrawObject 5 | 6 | internal interface PaintViewListener { 7 | fun onTouchUp(obj: DrawObject) 8 | fun onClick(x: Float, y: Float) 9 | fun onTouchEvent(event: MotionEvent) 10 | } -------------------------------------------------------------------------------- /canvaseditor/src/main/java/com/outsbook/libs/canvaseditor/listeners/StickerIconListener.kt: -------------------------------------------------------------------------------- 1 | package com.outsbook.libs.canvaseditor.listeners 2 | 3 | import android.view.MotionEvent 4 | import com.outsbook.libs.canvaseditor.stickers.StickerView 5 | 6 | internal interface StickerIconListener { 7 | fun onActionDown(stickerView: StickerView?, event: MotionEvent?) 8 | fun onActionMove(stickerView: StickerView, event: MotionEvent) 9 | fun onActionUp(stickerView: StickerView, event: MotionEvent?) 10 | } -------------------------------------------------------------------------------- /canvaseditor/src/main/java/com/outsbook/libs/canvaseditor/listeners/StickerViewListener.kt: -------------------------------------------------------------------------------- 1 | package com.outsbook.libs.canvaseditor.listeners 2 | 3 | import android.view.MotionEvent 4 | import com.outsbook.libs.canvaseditor.models.DrawObject 5 | 6 | internal interface StickerViewListener { 7 | fun onRemove() 8 | fun onDone(obj: DrawObject) 9 | fun onZoomAndRotate() 10 | fun onFlip() 11 | fun onClickStickerOutside(x: Float, y: Float) 12 | fun onTouchEvent(event: MotionEvent) 13 | } -------------------------------------------------------------------------------- /canvaseditor/src/main/java/com/outsbook/libs/canvaseditor/models/DrawObject.kt: -------------------------------------------------------------------------------- 1 | package com.outsbook.libs.canvaseditor.models 2 | 3 | import com.outsbook.libs.canvaseditor.enums.DrawType 4 | import com.outsbook.libs.canvaseditor.models.PathAndPaint 5 | import com.outsbook.libs.canvaseditor.stickers.Sticker 6 | 7 | internal data class DrawObject( 8 | val pathAndPaint: PathAndPaint?, 9 | val sticker: Sticker?, 10 | val drawType: DrawType 11 | ) -------------------------------------------------------------------------------- /canvaseditor/src/main/java/com/outsbook/libs/canvaseditor/models/PathAndPaint.kt: -------------------------------------------------------------------------------- 1 | package com.outsbook.libs.canvaseditor.models 2 | 3 | import android.graphics.Paint 4 | import android.graphics.Path 5 | 6 | internal data class PathAndPaint( 7 | val path: Path, 8 | val paint: Paint 9 | ) -------------------------------------------------------------------------------- /canvaseditor/src/main/java/com/outsbook/libs/canvaseditor/paints/PaintView.kt: -------------------------------------------------------------------------------- 1 | package com.outsbook.libs.canvaseditor.paints 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.graphics.Bitmap 6 | import android.graphics.Canvas 7 | import android.graphics.Paint 8 | import android.graphics.Path 9 | import android.util.Log 10 | import android.view.GestureDetector 11 | import android.view.MotionEvent 12 | import android.view.ViewConfiguration 13 | import android.widget.FrameLayout 14 | import androidx.core.content.ContextCompat 15 | import androidx.core.content.res.ResourcesCompat 16 | import com.outsbook.libs.canvaseditor.enums.DrawType 17 | import com.outsbook.libs.canvaseditor.listeners.PaintViewListener 18 | import com.outsbook.libs.canvaseditor.models.DrawObject 19 | import com.outsbook.libs.canvaseditor.models.PathAndPaint 20 | import com.outsbook.libs.canvaseditor.stickers.Sticker 21 | import kotlin.math.abs 22 | 23 | internal class PaintView (context: Context, private val paintViewListener: PaintViewListener) : 24 | FrameLayout(context) { 25 | 26 | private val drawColor = ResourcesCompat.getColor(resources, android.R.color.black, null) 27 | private var path = Path() 28 | private var motionTouchEventX = 0f 29 | private var motionTouchEventY = 0f 30 | private var currentX = 0f 31 | private var currentY = 0f 32 | private var isDrawPath = false 33 | private val touchTolerance = ViewConfiguration.get(context).scaledTouchSlop 34 | 35 | private lateinit var extraCanvas: Canvas 36 | lateinit var extraBitmap: Bitmap 37 | 38 | val paint = Paint().apply { 39 | color = drawColor 40 | isAntiAlias = true 41 | isDither = true 42 | style = Paint.Style.STROKE 43 | strokeJoin = Paint.Join.ROUND 44 | strokeCap = Paint.Cap.ROUND 45 | strokeWidth = 10f 46 | } 47 | 48 | fun initCanvas() { 49 | extraBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) 50 | extraCanvas = Canvas(extraBitmap) 51 | extraCanvas.drawColor(ContextCompat.getColor(context, android.R.color.white)) 52 | invalidate() 53 | } 54 | 55 | override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) { 56 | super.onSizeChanged(width, height, oldWidth, oldHeight) 57 | initCanvas() 58 | } 59 | 60 | override fun onDraw(canvas: Canvas) { 61 | super.onDraw(canvas) 62 | canvas.drawBitmap(extraBitmap, 0f, 0f, null) 63 | } 64 | 65 | fun drawPath(pathAndPaint: PathAndPaint) { 66 | extraCanvas.drawPath(pathAndPaint.path, pathAndPaint.paint) 67 | invalidate() 68 | } 69 | 70 | fun drawSticker(sticker: Sticker) { 71 | sticker.draw(extraCanvas) 72 | invalidate() 73 | } 74 | 75 | //region touch events 76 | private val gestureDetector = 77 | GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() { 78 | override fun onSingleTapConfirmed(event: MotionEvent?): Boolean { 79 | event?.let { 80 | paintViewListener.onClick(it.x, it.y) 81 | } 82 | return super.onSingleTapConfirmed(event) 83 | } 84 | }) 85 | 86 | @SuppressLint("ClickableViewAccessibility") 87 | override fun onTouchEvent(event: MotionEvent): Boolean { 88 | paintViewListener.onTouchEvent(event) 89 | motionTouchEventX = event.x 90 | motionTouchEventY = event.y 91 | when (event.action) { 92 | MotionEvent.ACTION_DOWN -> touchStart() 93 | MotionEvent.ACTION_MOVE -> touchMove() 94 | MotionEvent.ACTION_UP -> touchUp() 95 | } 96 | gestureDetector.onTouchEvent(event) 97 | return true 98 | } 99 | 100 | private fun touchStart() { 101 | path.reset() 102 | path.moveTo(motionTouchEventX, motionTouchEventY) 103 | currentX = motionTouchEventX 104 | currentY = motionTouchEventY 105 | } 106 | 107 | private fun touchMove() { 108 | val dx = abs(motionTouchEventX - currentX) 109 | val dy = abs(motionTouchEventY - currentY) 110 | if (dx >= touchTolerance || dy >= touchTolerance) { 111 | path.quadTo( 112 | currentX, 113 | currentY, 114 | (motionTouchEventX + currentX) / 2, 115 | (motionTouchEventY + currentY) / 2 116 | ) 117 | currentX = motionTouchEventX 118 | currentY = motionTouchEventY 119 | extraCanvas.drawPath(path, paint) 120 | isDrawPath = true 121 | } 122 | invalidate() 123 | } 124 | 125 | private fun touchUp() { 126 | if (isDrawPath) { 127 | val obj = DrawObject(PathAndPaint(Path(path), Paint(paint)), null, DrawType.PATH) 128 | paintViewListener.onTouchUp(obj) 129 | } 130 | invalidate() 131 | path.reset() 132 | isDrawPath = false 133 | } 134 | //endregion 135 | } -------------------------------------------------------------------------------- /canvaseditor/src/main/java/com/outsbook/libs/canvaseditor/stickers/BitmapSticker.kt: -------------------------------------------------------------------------------- 1 | package com.outsbook.libs.canvaseditor.stickers 2 | 3 | import android.content.Context 4 | import android.graphics.Bitmap 5 | import android.graphics.Canvas 6 | import android.graphics.Rect 7 | import android.graphics.drawable.BitmapDrawable 8 | import android.graphics.drawable.Drawable 9 | 10 | internal class BitmapSticker(context: Context, bitmap: Bitmap): Sticker() { 11 | private val realBounds: Rect 12 | 13 | override var drawable: Drawable = BitmapDrawable(context.resources, bitmap) 14 | 15 | final override val width: Int 16 | get() = drawable.intrinsicWidth 17 | 18 | final override val height: Int 19 | get() = drawable.intrinsicHeight 20 | 21 | init { 22 | realBounds = Rect(0, 0, width, height) 23 | } 24 | 25 | override fun setDrawable(drawable: Drawable): BitmapSticker { 26 | this.drawable = drawable 27 | return this 28 | } 29 | 30 | override fun draw(canvas: Canvas) { 31 | canvas.save() 32 | canvas.concat(matrix) 33 | drawable.bounds = realBounds 34 | drawable.draw(canvas) 35 | canvas.restore() 36 | } 37 | 38 | override fun setAlpha(alpha: Int): BitmapSticker { 39 | drawable.alpha = alpha 40 | return this 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /canvaseditor/src/main/java/com/outsbook/libs/canvaseditor/stickers/DrawableSticker.kt: -------------------------------------------------------------------------------- 1 | package com.outsbook.libs.canvaseditor.stickers 2 | 3 | import android.graphics.Canvas 4 | import android.graphics.Rect 5 | import android.graphics.drawable.Drawable 6 | 7 | internal open class DrawableSticker(override var drawable: Drawable): Sticker() { 8 | private val realBounds: Rect 9 | 10 | final override val width: Int 11 | get() = drawable.intrinsicWidth 12 | 13 | final override val height: Int 14 | get() = drawable.intrinsicHeight 15 | 16 | init { 17 | realBounds = Rect(0, 0, width, height) 18 | } 19 | 20 | override fun setDrawable(drawable: Drawable): DrawableSticker { 21 | this.drawable = drawable 22 | return this 23 | } 24 | 25 | override fun draw(canvas: Canvas) { 26 | canvas.save() 27 | canvas.concat(matrix) 28 | drawable.bounds = realBounds 29 | drawable.draw(canvas) 30 | canvas.restore() 31 | } 32 | 33 | override fun setAlpha(alpha: Int): DrawableSticker { 34 | drawable.alpha = alpha 35 | return this 36 | } 37 | } -------------------------------------------------------------------------------- /canvaseditor/src/main/java/com/outsbook/libs/canvaseditor/stickers/Sticker.kt: -------------------------------------------------------------------------------- 1 | package com.outsbook.libs.canvaseditor.stickers 2 | 3 | import android.graphics.Canvas 4 | import android.graphics.Matrix 5 | import android.graphics.PointF 6 | import android.graphics.RectF 7 | import android.graphics.drawable.Drawable 8 | import kotlin.math.atan2 9 | import kotlin.math.pow 10 | import kotlin.math.roundToInt 11 | import kotlin.math.sqrt 12 | 13 | internal abstract class Sticker { 14 | val matrix = Matrix() 15 | var isFlippedHorizontally = false 16 | var isFlippedVertically = false 17 | 18 | private val matrixValues = FloatArray(9) 19 | private val unrotatedWrapperCorner = FloatArray(8) 20 | private val unrotatedPoint = FloatArray(2) 21 | private val boundPoints = FloatArray(8) 22 | private val mappedBounds = FloatArray(8) 23 | private val trappedRect = RectF() 24 | 25 | abstract val width: Int 26 | abstract val height: Int 27 | abstract val drawable: Drawable 28 | 29 | val mappedBoundPoints: FloatArray 30 | get() { 31 | val dst = FloatArray(8) 32 | getMappedPoints(dst, getBoundPoints()) 33 | return dst 34 | } 35 | private val bound: RectF 36 | get() { 37 | val bound = RectF() 38 | getBound(bound) 39 | return bound 40 | } 41 | val mappedBound: RectF 42 | get() { 43 | val dst = RectF() 44 | getMappedBound(dst, bound) 45 | return dst 46 | } 47 | private val centerPoint: PointF 48 | get() { 49 | val center = PointF() 50 | getCenterPoint(center) 51 | return center 52 | } 53 | val mappedCenterPoint: PointF 54 | get() { 55 | val pointF = centerPoint 56 | getMappedCenterPoint(pointF, FloatArray(2), FloatArray(2)) 57 | return pointF 58 | } 59 | val currentScale: Float 60 | get() = getMatrixScale(matrix) 61 | val currentHeight: Float 62 | get() = getMatrixScale(matrix) * height 63 | val currentWidth: Float 64 | get() = getMatrixScale(matrix) * width 65 | private val currentAngle: Float 66 | get() = getMatrixAngle(matrix) 67 | 68 | companion object { 69 | fun getMatrix(sticker: Sticker): Matrix { 70 | return sticker.matrix 71 | } 72 | } 73 | 74 | abstract fun draw(canvas: Canvas) 75 | abstract fun setDrawable(drawable: Drawable): Sticker 76 | abstract fun setAlpha(alpha: Int): Sticker 77 | 78 | fun setMatrix(matrix: Matrix?): Sticker { 79 | this.matrix.set(matrix) 80 | return this 81 | } 82 | 83 | fun setFlippedHorizontally(flippedHorizontally: Boolean): Sticker { 84 | isFlippedHorizontally = flippedHorizontally 85 | return this 86 | } 87 | 88 | fun setFlippedVertically(flippedVertically: Boolean): Sticker { 89 | isFlippedVertically = flippedVertically 90 | return this 91 | } 92 | 93 | private fun getBoundPoints(): FloatArray { 94 | val points = FloatArray(8) 95 | getBoundPoints(points) 96 | return points 97 | } 98 | 99 | fun getBoundPoints(points: FloatArray) { 100 | if (!isFlippedHorizontally) { 101 | if (!isFlippedVertically) { 102 | points[0] = 0f 103 | points[1] = 0f 104 | points[2] = width.toFloat() 105 | points[3] = 0f 106 | points[4] = 0f 107 | points[5] = height.toFloat() 108 | points[6] = width.toFloat() 109 | points[7] = height.toFloat() 110 | } else { 111 | points[0] = 0f 112 | points[1] = height.toFloat() 113 | points[2] = width.toFloat() 114 | points[3] = height.toFloat() 115 | points[4] = 0f 116 | points[5] = 0f 117 | points[6] = width.toFloat() 118 | points[7] = 0f 119 | } 120 | } else { 121 | if (!isFlippedVertically) { 122 | points[0] = width.toFloat() 123 | points[1] = 0f 124 | points[2] = 0f 125 | points[3] = 0f 126 | points[4] = width.toFloat() 127 | points[5] = height.toFloat() 128 | points[6] = 0f 129 | points[7] = height.toFloat() 130 | } else { 131 | points[0] = width.toFloat() 132 | points[1] = height.toFloat() 133 | points[2] = 0f 134 | points[3] = height.toFloat() 135 | points[4] = width.toFloat() 136 | points[5] = 0f 137 | points[6] = 0f 138 | points[7] = 0f 139 | } 140 | } 141 | } 142 | 143 | fun getMappedPoints(src: FloatArray): FloatArray { 144 | val dst = FloatArray(src.size) 145 | matrix.mapPoints(dst, src) 146 | return dst 147 | } 148 | 149 | fun getMappedPoints(dst: FloatArray, src: FloatArray) { 150 | matrix.mapPoints(dst, src) 151 | } 152 | 153 | private fun getBound(dst: RectF) { 154 | dst[0f, 0f, width.toFloat()] = height.toFloat() 155 | } 156 | 157 | private fun getMappedBound(dst: RectF, bound: RectF) { 158 | matrix.mapRect(dst, bound) 159 | } 160 | 161 | fun getCenterPoint(dst: PointF) { 162 | dst[width * 1f / 2] = height * 1f / 2 163 | } 164 | 165 | fun getMappedCenterPoint(dst: PointF, mappedPoints: FloatArray, 166 | src: FloatArray) { 167 | getCenterPoint(dst) 168 | src[0] = dst.x 169 | src[1] = dst.y 170 | getMappedPoints(mappedPoints, src) 171 | dst[mappedPoints[0]] = mappedPoints[1] 172 | } 173 | 174 | private fun getMatrixScale(matrix: Matrix): Float { 175 | return sqrt( 176 | getMatrixValue(matrix, Matrix.MSCALE_X).toDouble().pow(2.0) + getMatrixValue( 177 | matrix, 178 | Matrix.MSKEW_Y 179 | ).toDouble().pow(2.0) 180 | ).toFloat() 181 | } 182 | 183 | private fun getMatrixAngle(matrix: Matrix): Float { 184 | return Math.toDegrees(-atan2(getMatrixValue(matrix, Matrix.MSKEW_X).toDouble(), 185 | getMatrixValue(matrix, Matrix.MSCALE_X).toDouble()) 186 | ).toFloat() 187 | } 188 | 189 | private fun getMatrixValue(matrix: Matrix, valueIndex: Int): Float { 190 | matrix.getValues(matrixValues) 191 | return matrixValues[valueIndex] 192 | } 193 | 194 | fun contains(x: Float, y: Float): Boolean { 195 | return contains(floatArrayOf(x, y)) 196 | } 197 | 198 | fun contains(point: FloatArray): Boolean { 199 | val tempMatrix = Matrix() 200 | tempMatrix.setRotate(-currentAngle) 201 | getBoundPoints(boundPoints) 202 | getMappedPoints(mappedBounds, boundPoints) 203 | tempMatrix.mapPoints(unrotatedWrapperCorner, mappedBounds) 204 | tempMatrix.mapPoints(unrotatedPoint, point) 205 | trapToRect(trappedRect, unrotatedWrapperCorner) 206 | return trappedRect.contains(unrotatedPoint[0], unrotatedPoint[1]) 207 | } 208 | 209 | private fun trapToRect(r: RectF, array: FloatArray) { 210 | r[Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY] = Float.NEGATIVE_INFINITY 211 | var i = 1 212 | while (i < array.size) { 213 | val x = (array[i - 1] * 10).roundToInt() / 10f 214 | val y = (array[i] * 10).roundToInt() / 10f 215 | r.left = if (x < r.left) x else r.left 216 | r.top = if (y < r.top) y else r.top 217 | r.right = if (x > r.right) x else r.right 218 | r.bottom = if (y > r.bottom) y else r.bottom 219 | i += 2 220 | } 221 | r.sort() 222 | } 223 | } -------------------------------------------------------------------------------- /canvaseditor/src/main/java/com/outsbook/libs/canvaseditor/stickers/StickerIcon.kt: -------------------------------------------------------------------------------- 1 | package com.outsbook.libs.canvaseditor.stickers 2 | 3 | import android.graphics.Canvas 4 | import android.graphics.Paint 5 | import android.graphics.drawable.Drawable 6 | import android.view.MotionEvent 7 | import com.outsbook.libs.canvaseditor.constants.ConstantStickerIcon 8 | import com.outsbook.libs.canvaseditor.listeners.StickerIconListener 9 | 10 | internal class StickerIcon(drawable: Drawable?, gravity: Int): DrawableSticker(drawable!!), StickerIconListener { 11 | var iconRadius = ConstantStickerIcon.DEFAULT_ICON_RADIUS 12 | var x = 0f 13 | var y = 0f 14 | 15 | var position = ConstantStickerIcon.LEFT_TOP 16 | var iconListener: StickerIconListener? = null 17 | 18 | init { 19 | position = gravity 20 | } 21 | 22 | fun draw(canvas: Canvas, paint: Paint?) { 23 | canvas.drawCircle(x, y, iconRadius, paint!!) 24 | super.draw(canvas) 25 | } 26 | 27 | override fun onActionDown(stickerView: StickerView?, event: MotionEvent?) { 28 | iconListener?.onActionDown(stickerView, event) 29 | } 30 | 31 | override fun onActionMove(stickerView: StickerView, event: MotionEvent) { 32 | iconListener?.onActionMove(stickerView, event) 33 | } 34 | 35 | override fun onActionUp(stickerView: StickerView, event: MotionEvent?) { 36 | iconListener?.onActionUp(stickerView, event) 37 | } 38 | } -------------------------------------------------------------------------------- /canvaseditor/src/main/java/com/outsbook/libs/canvaseditor/stickers/StickerView.kt: -------------------------------------------------------------------------------- 1 | package com.outsbook.libs.canvaseditor.stickers 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.graphics.* 6 | import android.view.MotionEvent 7 | import android.view.View 8 | import android.view.ViewConfiguration 9 | import android.widget.FrameLayout 10 | import androidx.core.content.ContextCompat 11 | import androidx.core.view.ViewCompat 12 | import com.outsbook.libs.canvaseditor.R 13 | import com.outsbook.libs.canvaseditor.constants.ActionMode 14 | import com.outsbook.libs.canvaseditor.constants.ConstantSticker 15 | import com.outsbook.libs.canvaseditor.constants.ConstantStickerIcon 16 | import com.outsbook.libs.canvaseditor.enums.DrawType 17 | import com.outsbook.libs.canvaseditor.events.DeleteIconEvent 18 | import com.outsbook.libs.canvaseditor.events.DoneIconEvent 19 | import com.outsbook.libs.canvaseditor.events.FlipIconEvent 20 | import com.outsbook.libs.canvaseditor.events.ZoomIconEvent 21 | import com.outsbook.libs.canvaseditor.listeners.StickerViewListener 22 | import com.outsbook.libs.canvaseditor.models.DrawObject 23 | import java.util.* 24 | import kotlin.math.abs 25 | import kotlin.math.atan2 26 | import kotlin.math.pow 27 | import kotlin.math.sqrt 28 | 29 | internal class StickerView(context: Context, private val stickerViewListener: StickerViewListener) : 30 | FrameLayout(context) { 31 | var currentSticker: Sticker? = null 32 | 33 | private var currentMode = ActionMode.NONE 34 | 35 | private var isTouchInsideSticker = false 36 | 37 | private val stickerRect = RectF() 38 | private val icons: MutableList = ArrayList(4) 39 | private val bitmapPoints = FloatArray(8) 40 | private val bounds = FloatArray(8) 41 | private val point = FloatArray(2) 42 | private val currentCenterPoint = PointF() 43 | private val tmp = FloatArray(2) 44 | private var midPoint = PointF() 45 | 46 | private val sizeMatrix = Matrix() 47 | private val downMatrix = Matrix() 48 | private val moveMatrix = Matrix() 49 | 50 | private var downX = 0f 51 | private var downY = 0f 52 | private var oldDistance = 0f 53 | private var oldRotation = 0f 54 | 55 | private val borderPaint = Paint().apply { 56 | isAntiAlias = true 57 | color = Color.BLACK 58 | alpha = 50 59 | } 60 | private val iconPaint = Paint().apply { 61 | isAntiAlias = true 62 | color = Color.BLACK 63 | alpha = 128 64 | } 65 | 66 | private val touchSlop: Int = ViewConfiguration.get(context).scaledTouchSlop 67 | private var currentIcon: StickerIcon? = null 68 | 69 | init { 70 | configDefaultIcons() 71 | } 72 | 73 | private fun configDefaultIcons() { 74 | val deleteIcon = StickerIcon( 75 | ContextCompat.getDrawable(context, R.drawable.ic_close_white_20dp), 76 | ConstantStickerIcon.LEFT_TOP 77 | ) 78 | deleteIcon.iconListener = DeleteIconEvent() 79 | val doneIcon = StickerIcon( 80 | ContextCompat.getDrawable(context, R.drawable.ic_done_white_20dp), 81 | ConstantStickerIcon.RIGHT_TOP 82 | ) 83 | doneIcon.iconListener = DoneIconEvent() 84 | val zoomIcon = StickerIcon( 85 | ContextCompat.getDrawable(context, R.drawable.ic_rotate_scale_white_17dp), 86 | ConstantStickerIcon.RIGHT_BOTTOM 87 | ) 88 | zoomIcon.iconListener = ZoomIconEvent() 89 | val flipIcon = StickerIcon( 90 | ContextCompat.getDrawable(context, R.drawable.ic_flip_white_20dp), 91 | ConstantStickerIcon.LEFT_BOTTOM 92 | ) 93 | flipIcon.iconListener = FlipIconEvent() 94 | icons.clear() 95 | icons.add(deleteIcon) 96 | icons.add(doneIcon) 97 | icons.add(zoomIcon) 98 | icons.add(flipIcon) 99 | } 100 | 101 | override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { 102 | super.onSizeChanged(w, h, oldw, oldh) 103 | currentSticker?.let { 104 | transformSticker(it) 105 | } 106 | } 107 | 108 | override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { 109 | super.onLayout(changed, left, top, right, bottom) 110 | if (changed) { 111 | stickerRect.left = left.toFloat() 112 | stickerRect.top = top.toFloat() 113 | stickerRect.right = right.toFloat() 114 | stickerRect.bottom = bottom.toFloat() 115 | } 116 | } 117 | 118 | override fun dispatchDraw(canvas: Canvas) { 119 | super.dispatchDraw(canvas) 120 | drawStickers(canvas) 121 | } 122 | 123 | override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { 124 | when (ev.action) { 125 | MotionEvent.ACTION_DOWN -> { 126 | downX = ev.x 127 | downY = ev.y 128 | return findCurrentIconTouched() != null || currentSticker != null 129 | } 130 | } 131 | return super.onInterceptTouchEvent(ev) 132 | } 133 | 134 | @SuppressLint("ClickableViewAccessibility") 135 | override fun onTouchEvent(event: MotionEvent): Boolean { 136 | stickerViewListener.onTouchEvent(event) 137 | when (event.actionMasked) { 138 | MotionEvent.ACTION_DOWN -> if (!onTouchDown(event)) { 139 | return false 140 | } 141 | MotionEvent.ACTION_POINTER_DOWN -> { 142 | oldDistance = calculateDistance(event) 143 | oldRotation = calculateRotation(event) 144 | midPoint = calculateMidPoint(event) 145 | if (currentSticker != null && isInStickerArea( 146 | currentSticker!!, event.getX(1), 147 | event.getY(1) 148 | ) && findCurrentIconTouched() == null 149 | ) { 150 | currentMode = ActionMode.ZOOM_WITH_TWO_FINGER 151 | } 152 | } 153 | MotionEvent.ACTION_MOVE -> { 154 | handleCurrentMode(event) 155 | invalidate() 156 | } 157 | MotionEvent.ACTION_UP -> onTouchUp(event) 158 | MotionEvent.ACTION_POINTER_UP -> { 159 | currentMode = ActionMode.NONE 160 | } 161 | } 162 | return true 163 | } 164 | 165 | private fun drawStickers(canvas: Canvas) { 166 | currentSticker?.draw(canvas) 167 | if (currentSticker != null) { 168 | getStickerPoints(currentSticker, bitmapPoints) 169 | val x1 = bitmapPoints[0] 170 | val y1 = bitmapPoints[1] 171 | val x2 = bitmapPoints[2] 172 | val y2 = bitmapPoints[3] 173 | val x3 = bitmapPoints[4] 174 | val y3 = bitmapPoints[5] 175 | val x4 = bitmapPoints[6] 176 | val y4 = bitmapPoints[7] 177 | 178 | //draw border 179 | canvas.drawLine(x1, y1, x2, y2, borderPaint) 180 | canvas.drawLine(x1, y1, x3, y3, borderPaint) 181 | canvas.drawLine(x2, y2, x4, y4, borderPaint) 182 | canvas.drawLine(x4, y4, x3, y3, borderPaint) 183 | 184 | //draw icons 185 | val rotation = calculateRotation(x4, y4, x3, y3) 186 | for (i in icons.indices) { 187 | val icon = icons[i] 188 | when (icon.position) { 189 | ConstantStickerIcon.LEFT_TOP -> configIconMatrix(icon, x1, y1, rotation) 190 | ConstantStickerIcon.RIGHT_TOP -> configIconMatrix(icon, x2, y2, rotation) 191 | ConstantStickerIcon.LEFT_BOTTOM -> configIconMatrix(icon, x3, y3, rotation) 192 | ConstantStickerIcon.RIGHT_BOTTOM -> configIconMatrix(icon, x4, y4, rotation) 193 | } 194 | icon.draw(canvas, iconPaint) 195 | } 196 | } 197 | } 198 | 199 | private fun getStickerPoints(sticker: Sticker?, dst: FloatArray) { 200 | if (sticker == null) { 201 | Arrays.fill(dst, 0f) 202 | return 203 | } 204 | sticker.getBoundPoints(bounds) 205 | sticker.getMappedPoints(dst, bounds) 206 | } 207 | 208 | private fun calculateDistance(event: MotionEvent?): Float { 209 | return if (event == null || event.pointerCount < 2) { 210 | 0f 211 | } else calculateDistance(event.getX(0), event.getY(0), event.getX(1), event.getY(1)) 212 | } 213 | 214 | private fun calculateDistance(x1: Float, y1: Float, x2: Float, y2: Float): Float { 215 | val x = x1 - x2.toDouble() 216 | val y = y1 - y2.toDouble() 217 | return sqrt(x * x + y * y).toFloat() 218 | } 219 | 220 | private fun calculateRotation(event: MotionEvent?): Float { 221 | return if (event == null || event.pointerCount < 2) { 222 | 0f 223 | } else calculateRotation(event.getX(0), event.getY(0), event.getX(1), event.getY(1)) 224 | } 225 | 226 | private fun calculateRotation(x1: Float, y1: Float, x2: Float, y2: Float): Float { 227 | val x = x1 - x2.toDouble() 228 | val y = y1 - y2.toDouble() 229 | val radians = atan2(y, x) 230 | return Math.toDegrees(radians).toFloat() 231 | } 232 | 233 | private fun configIconMatrix(icon: StickerIcon, x: Float, y: Float, rotation: Float) { 234 | icon.x = x 235 | icon.y = y 236 | icon.matrix.reset() 237 | icon.matrix.postRotate(rotation, icon.width / 2.toFloat(), icon.height / 2.toFloat()) 238 | icon.matrix.postTranslate(x - icon.width / 2, y - icon.height / 2) 239 | } 240 | 241 | private fun transformSticker(sticker: Sticker) { 242 | sizeMatrix.reset() 243 | val width = width.toFloat() 244 | val height = height.toFloat() 245 | val stickerWidth = sticker.width.toFloat() 246 | val stickerHeight = sticker.height.toFloat() 247 | //step 1 248 | val offsetX = (width - stickerWidth) / 2 249 | val offsetY = (height - stickerHeight) / 2 250 | sizeMatrix.postTranslate(offsetX, offsetY) 251 | 252 | //step 2 253 | val scaleFactor: Float 254 | scaleFactor = if (width < height) { 255 | width / stickerWidth 256 | } else { 257 | height / stickerHeight 258 | } 259 | sizeMatrix.postScale(scaleFactor / 2f, scaleFactor / 2f, width / 2f, height / 2f) 260 | sticker.matrix.reset() 261 | sticker.setMatrix(sizeMatrix) 262 | invalidate() 263 | } 264 | 265 | private fun findCurrentIconTouched(): StickerIcon? { 266 | for (icon in icons) { 267 | val x = icon.x - downX 268 | val y = icon.y - downY 269 | val distancePow2 = x * x + y * y 270 | if (distancePow2 <= (icon.iconRadius + icon.iconRadius.toDouble()).pow(2.0)) { 271 | return icon 272 | } 273 | } 274 | return null 275 | } 276 | 277 | private fun onTouchDown(event: MotionEvent): Boolean { 278 | currentMode = ActionMode.DRAG 279 | downX = event.x 280 | downY = event.y 281 | midPoint = calculateMidPoint() 282 | oldDistance = calculateDistance(midPoint.x, midPoint.y, downX, downY) 283 | oldRotation = calculateRotation(midPoint.x, midPoint.y, downX, downY) 284 | currentIcon = findCurrentIconTouched() 285 | 286 | if (currentIcon != null) { 287 | currentMode = ActionMode.ICON 288 | currentIcon!!.onActionDown(this, event) 289 | } 290 | 291 | if (currentSticker != null) { 292 | isTouchInsideSticker = currentSticker!!.contains(downX, downY) 293 | downMatrix.set(Sticker.getMatrix(currentSticker!!)) 294 | } 295 | 296 | if (currentIcon == null && !isTouchInsideSticker) { 297 | doneSticker(currentSticker) 298 | return false 299 | } 300 | 301 | invalidate() 302 | return true 303 | } 304 | 305 | private fun handleCurrentMode(event: MotionEvent) { 306 | when (currentMode) { 307 | ActionMode.NONE, ActionMode.CLICK -> { 308 | } 309 | ActionMode.DRAG -> if (currentSticker != null && isTouchInsideSticker) { 310 | moveMatrix.set(downMatrix) 311 | moveMatrix.postTranslate(event.x - downX, event.y - downY) 312 | currentSticker!!.setMatrix(moveMatrix) 313 | } 314 | 315 | ActionMode.ZOOM_WITH_TWO_FINGER -> if (currentSticker != null && isTouchInsideSticker) { 316 | val newDistance = calculateDistance(event) 317 | val newRotation = calculateRotation(event) 318 | moveMatrix.set(downMatrix) 319 | moveMatrix.postScale( 320 | newDistance / oldDistance, newDistance / oldDistance, midPoint.x, 321 | midPoint.y 322 | ) 323 | moveMatrix.postRotate(newRotation - oldRotation, midPoint.x, midPoint.y) 324 | currentSticker!!.setMatrix(moveMatrix) 325 | } 326 | ActionMode.ICON -> if (currentSticker != null && currentIcon != null) { 327 | currentIcon!!.onActionMove(this, event) 328 | } 329 | } 330 | } 331 | 332 | private fun onTouchUp(event: MotionEvent) { 333 | if (currentMode == ActionMode.ICON && currentIcon != null && currentSticker != null) { 334 | currentIcon!!.onActionUp(this, event) 335 | } 336 | if (currentMode == ActionMode.DRAG && abs(event.x - downX) < touchSlop && abs(event.y - downY) < touchSlop && currentSticker != null) { 337 | if (!isTouchInsideSticker) 338 | stickerViewListener.onClickStickerOutside(event.x, event.y) 339 | currentMode = ActionMode.CLICK 340 | } 341 | currentMode = ActionMode.NONE 342 | } 343 | 344 | private fun calculateMidPoint(event: MotionEvent?): PointF { 345 | if (event == null || event.pointerCount < 2) { 346 | midPoint[0f] = 0f 347 | return midPoint 348 | } 349 | val x = (event.getX(0) + event.getX(1)) / 2 350 | val y = (event.getY(0) + event.getY(1)) / 2 351 | midPoint[x] = y 352 | return midPoint 353 | } 354 | 355 | private fun calculateMidPoint(): PointF { 356 | if (currentSticker == null) { 357 | midPoint[0f] = 0f 358 | return midPoint 359 | } 360 | currentSticker?.getMappedCenterPoint(midPoint, point, tmp) 361 | return midPoint 362 | } 363 | 364 | private fun isInStickerArea(sticker: Sticker, downX: Float, downY: Float): Boolean { 365 | tmp[0] = downX 366 | tmp[1] = downY 367 | return sticker.contains(tmp) 368 | } 369 | 370 | fun addSticker(sticker: Sticker): StickerView { 371 | return addSticker(sticker, ConstantSticker.CENTER) 372 | } 373 | 374 | private fun addSticker(sticker: Sticker, position: Int): StickerView { 375 | if (ViewCompat.isLaidOut(this)) { 376 | addStickerImmediately(sticker, position) 377 | } else { 378 | post { addStickerImmediately(sticker, position) } 379 | } 380 | return this 381 | } 382 | 383 | private fun addStickerImmediately(sticker: Sticker, position: Int) { 384 | setStickerPosition(sticker, position) 385 | val scaleFactor: Float 386 | val widthScaleFactor: Float = width.toFloat() / sticker.drawable.intrinsicWidth 387 | val heightScaleFactor: Float = height.toFloat() / sticker.drawable.intrinsicHeight 388 | scaleFactor = 389 | if (widthScaleFactor > heightScaleFactor) heightScaleFactor else widthScaleFactor 390 | sticker.matrix.postScale( 391 | scaleFactor / 2, 392 | scaleFactor / 2, 393 | width / 2.toFloat(), 394 | height / 2.toFloat() 395 | ) 396 | currentSticker = sticker 397 | //stickers.add(sticker) 398 | invalidate() 399 | } 400 | 401 | private fun setStickerPosition(sticker: Sticker, position: Int) { 402 | val width = width.toFloat() 403 | val height = height.toFloat() 404 | var offsetX = width - sticker.width 405 | var offsetY = height - sticker.height 406 | when { 407 | position and ConstantSticker.TOP > 0 -> offsetY /= 4f 408 | position and ConstantSticker.BOTTOM > 0 -> offsetY *= 3f / 4f 409 | else -> offsetY /= 2f 410 | } 411 | when { 412 | position and ConstantSticker.LEFT > 0 -> offsetX /= 4f 413 | position and ConstantSticker.RIGHT > 0 -> offsetX *= 3f / 4f 414 | else -> offsetX /= 2f 415 | } 416 | sticker.matrix.postTranslate(offsetX, offsetY) 417 | } 418 | 419 | private fun removeSticker(sticker: Sticker?) { 420 | if (sticker == null) 421 | return 422 | currentSticker = null 423 | this.visibility = View.GONE 424 | stickerViewListener.onRemove() 425 | } 426 | 427 | private fun doneSticker(sticker: Sticker?) { 428 | if (sticker == null) 429 | return 430 | currentSticker = null 431 | this.visibility = View.GONE 432 | val obj = DrawObject(null, sticker, DrawType.STICKER) 433 | stickerViewListener.onDone(obj) 434 | } 435 | 436 | private fun zoomAndRotateSticker(sticker: Sticker?, event: MotionEvent) { 437 | if (sticker == null) 438 | return 439 | val newDistance = calculateDistance(midPoint.x, midPoint.y, event.x, event.y) 440 | val newRotation = calculateRotation(midPoint.x, midPoint.y, event.x, event.y) 441 | moveMatrix.set(downMatrix) 442 | moveMatrix.postScale( 443 | newDistance / oldDistance, newDistance / oldDistance, midPoint.x, 444 | midPoint.y 445 | ) 446 | moveMatrix.postRotate(newRotation - oldRotation, midPoint.x, midPoint.y) 447 | currentSticker!!.setMatrix(moveMatrix) 448 | stickerViewListener.onZoomAndRotate() 449 | } 450 | 451 | private fun flipSticker(sticker: Sticker?) { 452 | if (sticker == null) 453 | return 454 | sticker.getCenterPoint(midPoint) 455 | sticker.matrix.preScale(-1f, 1f, midPoint.x, midPoint.y) 456 | sticker.isFlippedHorizontally = !sticker.isFlippedHorizontally 457 | invalidate() 458 | stickerViewListener.onFlip() 459 | } 460 | 461 | fun remove() { 462 | removeSticker(currentSticker) 463 | } 464 | 465 | fun done() { 466 | doneSticker(currentSticker) 467 | } 468 | 469 | fun zoomAndRotate(event: MotionEvent) { 470 | zoomAndRotateSticker(currentSticker, event) 471 | } 472 | 473 | fun flip() { 474 | flipSticker(currentSticker) 475 | } 476 | } -------------------------------------------------------------------------------- /canvaseditor/src/main/java/com/outsbook/libs/canvaseditor/stickers/TextSticker.kt: -------------------------------------------------------------------------------- 1 | package com.outsbook.libs.canvaseditor.stickers 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.Rect 6 | import android.graphics.Typeface 7 | import android.graphics.drawable.Drawable 8 | import android.os.Build 9 | import android.text.Layout 10 | import android.text.StaticLayout 11 | import android.text.TextPaint 12 | import androidx.core.content.ContextCompat 13 | import com.outsbook.libs.canvaseditor.R 14 | 15 | internal class TextSticker(private val context: Context, drawable: Drawable?): Sticker() { 16 | override lateinit var drawable: Drawable 17 | 18 | private var text: String? = null 19 | private val mEllipsis = "\u2026" 20 | 21 | private val realBounds: Rect 22 | private val textRect: Rect 23 | private val textPaint: TextPaint 24 | private var staticLayout: StaticLayout? = null 25 | private var alignment: Layout.Alignment 26 | 27 | private var maxTextSizePixels: Float 28 | private var minTextSizePixels: Float 29 | private var lineSpacingMultiplier = 1.0f 30 | private var lineSpacingExtra = 0.0f 31 | 32 | init { 33 | if (drawable == null) { 34 | this.drawable = ContextCompat.getDrawable(context, R.drawable.shape_transfarent_background)!! 35 | }else{ 36 | this.drawable = drawable 37 | } 38 | textPaint = TextPaint(TextPaint.ANTI_ALIAS_FLAG) 39 | realBounds = Rect(0, 0, width, height) 40 | textRect = Rect(0, 0, width, height) 41 | minTextSizePixels = convertSpToPx(6f) 42 | maxTextSizePixels = convertSpToPx(32f) 43 | alignment = Layout.Alignment.ALIGN_CENTER 44 | textPaint.textSize = maxTextSizePixels 45 | } 46 | 47 | override val width: Int 48 | get() = drawable.intrinsicWidth 49 | 50 | override val height: Int 51 | get() = drawable.intrinsicHeight 52 | 53 | override fun draw(canvas: Canvas) { 54 | val matrix = matrix 55 | canvas.save() 56 | canvas.concat(matrix) 57 | drawable.bounds = realBounds 58 | drawable.draw(canvas) 59 | 60 | canvas.restore() 61 | canvas.save() 62 | canvas.concat(matrix) 63 | if (textRect.width() == width) { 64 | val dy = height / 2 - staticLayout!!.height / 2 65 | canvas.translate(0f, dy.toFloat()) 66 | } else { 67 | val dx = textRect.left 68 | val dy = textRect.top + textRect.height() / 2 - staticLayout!!.height / 2 69 | canvas.translate(dx.toFloat(), dy.toFloat()) 70 | } 71 | staticLayout!!.draw(canvas) 72 | canvas.restore() 73 | } 74 | 75 | override fun setAlpha(alpha: Int): TextSticker { 76 | textPaint.alpha = alpha 77 | return this 78 | } 79 | 80 | override fun setDrawable(drawable: Drawable): TextSticker { 81 | this.drawable = drawable 82 | realBounds[0, 0, width] = height 83 | textRect[0, 0, width] = height 84 | return this 85 | } 86 | 87 | fun setDrawable(drawable: Drawable, region: Rect?): TextSticker { 88 | this.drawable = drawable 89 | realBounds[0, 0, width] = height 90 | if (region == null) { 91 | textRect[0, 0, width] = height 92 | } else { 93 | textRect[region.left, region.top, region.right] = region.bottom 94 | } 95 | return this 96 | } 97 | 98 | fun setText(text: String): TextSticker{ 99 | this.text = text 100 | return this 101 | } 102 | 103 | fun setTypeface(typeface: Typeface?): TextSticker { 104 | textPaint.typeface = typeface 105 | return this 106 | } 107 | 108 | fun setTextColor(color: Int): TextSticker { 109 | textPaint.color = color 110 | return this 111 | } 112 | 113 | fun setTextAlign(alignment: Layout.Alignment): TextSticker { 114 | this.alignment = alignment 115 | return this 116 | } 117 | 118 | fun setMaxTextSize(size: Float): TextSticker { 119 | textPaint.textSize = convertSpToPx(size) 120 | maxTextSizePixels = textPaint.textSize 121 | return this 122 | } 123 | 124 | fun setMinTextSize(minTextSizeScaledPixels: Float): TextSticker { 125 | minTextSizePixels = convertSpToPx(minTextSizeScaledPixels) 126 | return this 127 | } 128 | 129 | fun setLineSpacing(add: Float, multiplier: Float): TextSticker { 130 | lineSpacingMultiplier = multiplier 131 | lineSpacingExtra = add 132 | return this 133 | } 134 | 135 | fun resizeText(): TextSticker { 136 | val availableHeightPixels = textRect.height() 137 | val availableWidthPixels = textRect.width() 138 | val text: CharSequence? = text 139 | if (text == null || text.isEmpty() || availableHeightPixels <= 0 || availableWidthPixels <= 0 || maxTextSizePixels <= 0) { 140 | return this 141 | } 142 | var targetTextSizePixels = maxTextSizePixels 143 | var targetTextHeightPixels = getTextHeightPixels(text, availableWidthPixels, targetTextSizePixels) 144 | 145 | while (targetTextHeightPixels > availableHeightPixels 146 | && targetTextSizePixels > minTextSizePixels) { 147 | targetTextSizePixels = Math.max(targetTextSizePixels - 2, minTextSizePixels) 148 | targetTextHeightPixels = getTextHeightPixels(text, availableWidthPixels, targetTextSizePixels) 149 | } 150 | 151 | if (targetTextSizePixels == minTextSizePixels 152 | && targetTextHeightPixels > availableHeightPixels) { 153 | val textPaintCopy = TextPaint(textPaint) 154 | textPaintCopy.textSize = targetTextSizePixels 155 | 156 | val staticLayout = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 157 | StaticLayout.Builder 158 | .obtain(text,0, text.length, textPaintCopy, availableWidthPixels) 159 | .setAlignment(Layout.Alignment.ALIGN_NORMAL) 160 | .setLineSpacing(lineSpacingExtra, lineSpacingMultiplier) 161 | .setIncludePad(false) 162 | .build() 163 | } else { 164 | @Suppress("DEPRECATION") 165 | (StaticLayout( 166 | text, textPaintCopy, availableWidthPixels, Layout.Alignment.ALIGN_NORMAL, 167 | lineSpacingMultiplier, lineSpacingExtra, false 168 | )) 169 | } 170 | 171 | if (staticLayout.lineCount > 0) { 172 | val lastLine = staticLayout.getLineForVertical(availableHeightPixels) - 1 173 | if (lastLine >= 0) { 174 | val startOffset = staticLayout.getLineStart(lastLine) 175 | var endOffset = staticLayout.getLineEnd(lastLine) 176 | var lineWidthPixels = staticLayout.getLineWidth(lastLine) 177 | val ellipseWidth = textPaintCopy.measureText(mEllipsis) 178 | 179 | while (availableWidthPixels < lineWidthPixels + ellipseWidth) { 180 | endOffset-- 181 | lineWidthPixels = textPaintCopy.measureText(text.subSequence(startOffset, endOffset + 1).toString()) 182 | } 183 | setText(text.subSequence(0, endOffset).toString() + mEllipsis) 184 | } 185 | } 186 | } 187 | textPaint.textSize = targetTextSizePixels 188 | staticLayout = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 189 | StaticLayout.Builder 190 | .obtain(this.text!!,0, this.text!!.length, textPaint, textRect.width()) 191 | .setAlignment(alignment) 192 | .setLineSpacing(lineSpacingExtra, lineSpacingMultiplier) 193 | .setIncludePad(true) 194 | .build() 195 | } else { 196 | @Suppress("DEPRECATION") 197 | (StaticLayout( 198 | this.text!!, textPaint, availableWidthPixels, alignment, 199 | lineSpacingMultiplier, lineSpacingExtra, true 200 | )) 201 | } 202 | return this 203 | } 204 | 205 | private fun getTextHeightPixels(source: CharSequence, availableWidthPixels: Int, textSizePixels: Float): Int { 206 | textPaint.textSize = textSizePixels 207 | val staticLayout = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 208 | StaticLayout.Builder 209 | .obtain(source,0, 0, textPaint, availableWidthPixels) 210 | .setAlignment(Layout.Alignment.ALIGN_NORMAL) 211 | .setLineSpacing(lineSpacingExtra, lineSpacingMultiplier) 212 | .setIncludePad(true) 213 | .build() 214 | } else { 215 | @Suppress("DEPRECATION") 216 | (StaticLayout( 217 | source, textPaint, availableWidthPixels, Layout.Alignment.ALIGN_NORMAL, 218 | lineSpacingMultiplier, lineSpacingExtra, true 219 | )) 220 | } 221 | 222 | return staticLayout.height 223 | } 224 | 225 | private fun convertSpToPx(scaledPixels: Float): Float { 226 | return scaledPixels * context.resources.displayMetrics.scaledDensity 227 | } 228 | } -------------------------------------------------------------------------------- /canvaseditor/src/main/res/drawable/ic_close_white_20dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /canvaseditor/src/main/res/drawable/ic_done_white_20dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /canvaseditor/src/main/res/drawable/ic_flip_white_20dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /canvaseditor/src/main/res/drawable/ic_rotate_scale_white_17dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 18 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /canvaseditor/src/main/res/drawable/shape_transfarent_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /canvaseditor/src/test/java/com/outsbook/libs/canvaseditor/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.outsbook.libs.canvaseditor 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 | -------------------------------------------------------------------------------- /example-java/.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 | .cxx 15 | -------------------------------------------------------------------------------- /example-java/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /example-java/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 29 5 | buildToolsVersion "29.0.2" 6 | 7 | defaultConfig { 8 | applicationId "com.outsbook.examplejava" 9 | minSdkVersion 23 10 | targetSdkVersion 29 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | 24 | compileOptions { 25 | sourceCompatibility JavaVersion.VERSION_1_8 26 | targetCompatibility JavaVersion.VERSION_1_8 27 | } 28 | 29 | } 30 | 31 | dependencies { 32 | implementation fileTree(dir: 'libs', include: ['*.jar']) 33 | 34 | implementation 'androidx.appcompat:appcompat:1.1.0' 35 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 36 | testImplementation 'junit:junit:4.13' 37 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 38 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 39 | 40 | implementation 'com.outsbook.libs:canvaseditor:1.0.0' 41 | } 42 | -------------------------------------------------------------------------------- /example-java/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 | -------------------------------------------------------------------------------- /example-java/app/src/androidTest/java/com/outsbook/examplejava/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.outsbook.examplejava; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | 25 | assertEquals("com.outsbook.examplejava", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example-java/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /example-java/app/src/main/java/com/outsbook/examplejava/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.outsbook.examplejava; 2 | 3 | import androidx.appcompat.app.AppCompatActivity; 4 | import androidx.core.content.ContextCompat; 5 | import android.graphics.Bitmap; 6 | import android.graphics.drawable.Drawable; 7 | import android.os.Bundle; 8 | import android.view.MotionEvent; 9 | import android.view.View; 10 | import android.widget.ImageButton; 11 | import android.widget.ImageView; 12 | 13 | import com.outsbook.libs.canvaseditor.CanvasEditorView; 14 | import com.outsbook.libs.canvaseditor.listeners.CanvasEditorListener; 15 | 16 | public class MainActivity extends AppCompatActivity { 17 | 18 | private CanvasEditorView canvasEditor; 19 | 20 | private ImageButton buttonSticker; 21 | private ImageButton buttonText; 22 | private ImageButton buttonStickerText; 23 | private ImageButton buttonBlack; 24 | private ImageButton buttonYellow; 25 | private ImageButton buttonPlus; 26 | private ImageButton buttonMinus; 27 | 28 | private ImageButton buttonSave; 29 | private ImageButton buttonUndo; 30 | private ImageButton buttonRedo; 31 | private ImageButton buttonDelete; 32 | 33 | private View viewImagePreview; 34 | private ImageButton buttonClose; 35 | private ImageView imageView; 36 | 37 | private Float strokeWidth = 20f; 38 | 39 | @Override 40 | protected void onCreate(Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | setContentView(R.layout.activity_main); 43 | initView(); 44 | initValue(); 45 | initClickListener(); 46 | initCanvasEditorListener(); 47 | } 48 | 49 | private void initView(){ 50 | canvasEditor = findViewById(R.id.canvasEditor); 51 | 52 | buttonSticker = findViewById(R.id.buttonSticker); 53 | buttonText = findViewById(R.id.buttonText); 54 | buttonStickerText = findViewById(R.id.buttonStickerText); 55 | buttonBlack = findViewById(R.id.buttonBlack); 56 | buttonYellow = findViewById(R.id.buttonYellow); 57 | buttonPlus = findViewById(R.id.buttonPlus); 58 | buttonMinus = findViewById(R.id.buttonMinus); 59 | 60 | buttonSave = findViewById(R.id.buttonSave); 61 | buttonUndo = findViewById(R.id.buttonUndo); 62 | buttonRedo = findViewById(R.id.buttonRedo); 63 | buttonDelete = findViewById(R.id.buttonDelete); 64 | 65 | viewImagePreview = findViewById(R.id.viewImagePreview); 66 | buttonClose = findViewById(R.id.buttonClose); 67 | imageView = findViewById(R.id.imageView); 68 | } 69 | 70 | private void initValue(){ 71 | buttonUndo.setImageAlpha(50); 72 | buttonRedo.setImageAlpha(50); 73 | //set stroke width 74 | canvasEditor.setStrokeWidth(strokeWidth); 75 | //set paint color 76 | canvasEditor.setPaintColor(ContextCompat.getColor(this, R.color.colorBlack)); 77 | } 78 | 79 | private void initClickListener(){ 80 | buttonSticker.setOnClickListener(v -> { 81 | Drawable drawable = ContextCompat.getDrawable(this, R.drawable.app_icon); 82 | if(drawable != null) 83 | canvasEditor.addDrawableSticker(drawable); 84 | }); 85 | buttonText.setOnClickListener(v -> { 86 | String text = "Canvas"; 87 | int color = ContextCompat.getColor(this, R.color.colorPrimary); 88 | canvasEditor.addTextSticker(text, color, null); 89 | }); 90 | buttonStickerText.setOnClickListener(v -> { 91 | Drawable drawable = ContextCompat.getDrawable(this, R.drawable.ic_panorama_240dp); 92 | String text = "Canvas"; 93 | int textColor = ContextCompat.getColor(this, R.color.colorAccent); 94 | if(drawable != null) 95 | canvasEditor.addDrawableTextSticker(drawable, text, textColor, null); 96 | }); 97 | buttonBlack.setOnClickListener(v -> { 98 | buttonPlus.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_plus_black_24dp)); 99 | buttonMinus.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_minus_black_24dp)); 100 | int color = ContextCompat.getColor(this, R.color.colorBlack); 101 | canvasEditor.setPaintColor(color); 102 | }); 103 | buttonYellow.setOnClickListener(v -> { 104 | buttonPlus.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_plus_yellow_24dp)); 105 | buttonMinus.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_minus_yellow_24dp)); 106 | int color = ContextCompat.getColor(this, R.color.colorYellow); 107 | canvasEditor.setPaintColor(color); 108 | }); 109 | buttonPlus.setOnClickListener(v -> { 110 | strokeWidth += 10f; 111 | canvasEditor.setStrokeWidth(strokeWidth); 112 | }); 113 | buttonMinus.setOnClickListener(v -> { 114 | strokeWidth -= 10f; 115 | canvasEditor.setStrokeWidth(strokeWidth); 116 | }); 117 | 118 | buttonSave.setOnClickListener(v -> { 119 | Bitmap bitmap = canvasEditor.downloadBitmap(); 120 | imageView.setImageBitmap(bitmap); 121 | viewImagePreview.setVisibility(View.VISIBLE); 122 | }); 123 | buttonUndo.setOnClickListener(v -> { 124 | canvasEditor.undo(); 125 | }); 126 | buttonRedo.setOnClickListener(v -> { 127 | canvasEditor.redo(); 128 | }); 129 | buttonDelete.setOnClickListener(v -> { 130 | canvasEditor.removeAll(); 131 | }); 132 | 133 | buttonClose.setOnClickListener(v -> { 134 | viewImagePreview.setVisibility(View.GONE); 135 | }); 136 | } 137 | 138 | private void initCanvasEditorListener(){ 139 | canvasEditor.setListener(new CanvasEditorListener() { 140 | @Override 141 | public void onEnableUndo(boolean isEnable) { 142 | // isEnable = true (undo list is not empty) 143 | // isEnable = false (undo list is empty) 144 | buttonUndo.setImageAlpha(isEnable? 255 : 50); 145 | } 146 | 147 | @Override 148 | public void onEnableRedo(boolean isEnable) { 149 | // isEnable = true (redo list is not empty) 150 | // isEnable = false (redo list is empty) 151 | buttonRedo.setImageAlpha(isEnable? 255 : 50); 152 | } 153 | 154 | @Override 155 | public void onTouchEvent(MotionEvent motionEvent) { 156 | //When the canvas touch 157 | } 158 | 159 | @Override 160 | public void onStickerActive() { 161 | //When a sticker change to active mode 162 | } 163 | 164 | @Override 165 | public void onStickerRemove() { 166 | //When a sticker remove from canvas 167 | } 168 | 169 | @Override 170 | public void onStickerDone() { 171 | //When the active sticker added to canvas 172 | } 173 | 174 | @Override 175 | public void onStickerZoomAndRotate() { 176 | //When the active sticker zoom or rotate 177 | } 178 | 179 | @Override 180 | public void onStickerFlip() { 181 | //When the active sticker flip 182 | } 183 | }); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /example-java/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /example-java/app/src/main/res/drawable/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-java/app/src/main/res/drawable/app_icon.png -------------------------------------------------------------------------------- /example-java/app/src/main/res/drawable/ic_cancel_yellow_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example-java/app/src/main/res/drawable/ic_color_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /example-java/app/src/main/res/drawable/ic_color_yellow_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example-java/app/src/main/res/drawable/ic_delete_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example-java/app/src/main/res/drawable/ic_download_gray_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example-java/app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /example-java/app/src/main/res/drawable/ic_minus_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /example-java/app/src/main/res/drawable/ic_minus_yellow_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example-java/app/src/main/res/drawable/ic_panorama_240dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example-java/app/src/main/res/drawable/ic_plus_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /example-java/app/src/main/res/drawable/ic_plus_yellow_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example-java/app/src/main/res/drawable/ic_redo_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example-java/app/src/main/res/drawable/ic_sticker_text_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example-java/app/src/main/res/drawable/ic_sticker_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example-java/app/src/main/res/drawable/ic_text_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example-java/app/src/main/res/drawable/ic_undo_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example-java/app/src/main/res/drawable/shape_delete_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /example-java/app/src/main/res/drawable/shape_redo_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example-java/app/src/main/res/drawable/shape_undo_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /example-java/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 25 | 26 | 36 | 37 | 47 | 48 | 56 | 65 | 74 | 83 | 92 | 101 | 110 | 119 | 128 | 129 | 130 | 136 | 143 | 144 | 148 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /example-java/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /example-java/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /example-java/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-java/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example-java/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-java/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /example-java/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-java/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example-java/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-java/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example-java/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-java/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /example-java/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-java/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example-java/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-java/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example-java/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-java/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /example-java/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-java/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example-java/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-java/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example-java/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-java/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /example-java/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-java/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example-java/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-java/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example-java/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-java/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /example-java/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-java/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example-java/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #DB2659 4 | #DB2659 5 | #FEC63B 6 | 7 | #000000 8 | #FEC63B 9 | 10 | -------------------------------------------------------------------------------- /example-java/app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #F7F7F7 4 | -------------------------------------------------------------------------------- /example-java/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Canvas Editor 3 | 4 | -------------------------------------------------------------------------------- /example-java/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /example-java/app/src/test/java/com/outsbook/examplejava/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.outsbook.examplejava; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /example-java/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | google() 7 | jcenter() 8 | 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.6.3' 12 | 13 | 14 | // NOTE: Do not place your application dependencies here; they belong 15 | // in the individual module build.gradle files 16 | } 17 | } 18 | 19 | allprojects { 20 | repositories { 21 | google() 22 | jcenter() 23 | 24 | } 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /example-java/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 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | 21 | -------------------------------------------------------------------------------- /example-java/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-java/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /example-java/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri May 01 00:22:55 BDT 2020 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.6.4-all.zip 7 | -------------------------------------------------------------------------------- /example-java/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 | -------------------------------------------------------------------------------- /example-java/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 | -------------------------------------------------------------------------------- /example-java/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name='example-java' 2 | include ':app' 3 | -------------------------------------------------------------------------------- /example-kotlin/.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 | .cxx 15 | -------------------------------------------------------------------------------- /example-kotlin/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /example-kotlin/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 29 7 | buildToolsVersion "29.0.2" 8 | 9 | defaultConfig { 10 | applicationId "com.outsbook.examplekotlin" 11 | minSdkVersion 23 12 | targetSdkVersion 29 13 | versionCode 1 14 | versionName "1.0.0" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | 26 | } 27 | 28 | dependencies { 29 | implementation fileTree(dir: 'libs', include: ['*.jar']) 30 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 31 | implementation 'androidx.appcompat:appcompat:1.1.0' 32 | implementation 'androidx.core:core-ktx:1.2.0' 33 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 34 | testImplementation 'junit:junit:4.13' 35 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 36 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 37 | 38 | implementation 'com.outsbook.libs:canvaseditor:1.0.0' 39 | } 40 | -------------------------------------------------------------------------------- /example-kotlin/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 | -------------------------------------------------------------------------------- /example-kotlin/app/src/androidTest/java/com/outsbook/examplekotlin/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.outsbook.examplekotlin 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.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.getInstrumentation().targetContext 22 | assertEquals("com.outsbook.examplekotlin", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/java/com/outsbook/examplekotlin/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.outsbook.examplekotlin 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import android.os.Bundle 5 | import android.view.MotionEvent 6 | import android.view.View 7 | import androidx.core.content.ContextCompat 8 | import com.outsbook.libs.canvaseditor.listeners.CanvasEditorListener 9 | import kotlinx.android.synthetic.main.activity_main.* 10 | 11 | class MainActivity : AppCompatActivity() { 12 | 13 | private var strokeWidth: Float = 20f 14 | 15 | override fun onCreate(savedInstanceState: Bundle?) { 16 | super.onCreate(savedInstanceState) 17 | setContentView(R.layout.activity_main) 18 | initValue() 19 | initClickListener() 20 | initCanvasEditorListener() 21 | } 22 | 23 | private fun initValue(){ 24 | buttonUndo.imageAlpha = 50 25 | buttonRedo.imageAlpha = 50 26 | //set stroke width 27 | canvasEditor.setStrokeWidth(strokeWidth) 28 | //set paint color 29 | canvasEditor.setPaintColor(ContextCompat.getColor(this, R.color.colorBlack)) 30 | } 31 | 32 | private fun initClickListener(){ 33 | buttonSticker.setOnClickListener{ 34 | //Add drawable sticker 35 | val drawable = ContextCompat.getDrawable(this, R.drawable.app_icon) 36 | drawable?.let { 37 | canvasEditor.addDrawableSticker(it) 38 | } 39 | } 40 | 41 | buttonText.setOnClickListener{ 42 | //Add text sticker 43 | val text = "Canvas" 44 | val textColor = ContextCompat.getColor(this, R.color.colorPrimary) 45 | canvasEditor.addTextSticker(text, textColor, null) 46 | } 47 | 48 | buttonStickerText.setOnClickListener{ 49 | //Add text with drawable sticker 50 | val drawable = ContextCompat.getDrawable(this, R.drawable.ic_panorama_240dp) 51 | val text = "Canvas" 52 | val textColor = ContextCompat.getColor(this, R.color.colorAccent) 53 | drawable?.let{ 54 | canvasEditor.addDrawableTextSticker(it, text, textColor, null) 55 | } 56 | } 57 | 58 | buttonBlack.setOnClickListener { 59 | buttonPlus.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_plus_black_24dp)) 60 | buttonMinus.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_minus_black_24dp)) 61 | val color = ContextCompat.getColor(this, R.color.colorBlack) 62 | canvasEditor.setPaintColor(color) 63 | } 64 | 65 | buttonYellow.setOnClickListener { 66 | buttonPlus.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_plus_yellow_24dp)) 67 | buttonMinus.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_minus_yellow_24dp)) 68 | val color = ContextCompat.getColor(this, R.color.colorYellow) 69 | canvasEditor.setPaintColor(color) 70 | } 71 | 72 | buttonPlus.setOnClickListener { 73 | strokeWidth += 10f 74 | canvasEditor.setStrokeWidth(strokeWidth) 75 | } 76 | 77 | buttonMinus.setOnClickListener { 78 | strokeWidth -= 10f 79 | canvasEditor.setStrokeWidth(strokeWidth) 80 | } 81 | 82 | buttonSave.setOnClickListener { 83 | val bitmap = canvasEditor.downloadBitmap() 84 | imageView.setImageBitmap(bitmap) 85 | viewImagePreview.visibility = View.VISIBLE 86 | } 87 | 88 | buttonUndo.setOnClickListener { 89 | canvasEditor.undo() 90 | } 91 | 92 | buttonDelete.setOnClickListener { 93 | canvasEditor.removeAll() 94 | } 95 | 96 | buttonRedo.setOnClickListener { 97 | canvasEditor.redo() 98 | } 99 | 100 | buttonClose.setOnClickListener { 101 | viewImagePreview.visibility = View.GONE 102 | } 103 | } 104 | 105 | private fun initCanvasEditorListener(){ 106 | canvasEditor.setListener(object: CanvasEditorListener { 107 | override fun onEnableUndo(isEnable: Boolean) { 108 | // isEnable = true (undo list is not empty) 109 | // isEnable = false (undo list is empty) 110 | buttonUndo.imageAlpha = if(isEnable) 255 else 50 111 | } 112 | 113 | override fun onEnableRedo(isEnable: Boolean) { 114 | // isEnable = true (redo list is not empty) 115 | // isEnable = false (redo list is empty) 116 | buttonRedo.imageAlpha = if(isEnable) 255 else 50 117 | } 118 | 119 | override fun onTouchEvent(event: MotionEvent) { 120 | //When the canvas touch 121 | } 122 | 123 | override fun onStickerActive() { 124 | //When a sticker change to active mode 125 | } 126 | 127 | override fun onStickerRemove() { 128 | //When a sticker remove from canvas 129 | } 130 | 131 | override fun onStickerDone() { 132 | //When the active sticker added to canvas 133 | } 134 | 135 | override fun onStickerZoomAndRotate() { 136 | //When the active sticker zoom or rotate 137 | } 138 | 139 | override fun onStickerFlip() { 140 | //When the active sticker flip 141 | } 142 | }) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/drawable/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-kotlin/app/src/main/res/drawable/app_icon.png -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/drawable/ic_cancel_yellow_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/drawable/ic_color_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/drawable/ic_color_yellow_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/drawable/ic_delete_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/drawable/ic_download_gray_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/drawable/ic_minus_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/drawable/ic_minus_yellow_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/drawable/ic_panorama_240dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/drawable/ic_plus_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/drawable/ic_plus_yellow_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/drawable/ic_redo_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/drawable/ic_sticker_text_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/drawable/ic_sticker_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/drawable/ic_text_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/drawable/ic_undo_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/drawable/shape_delete_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/drawable/shape_redo_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/drawable/shape_undo_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 25 | 26 | 36 | 37 | 47 | 48 | 56 | 65 | 74 | 83 | 92 | 101 | 110 | 119 | 128 | 129 | 130 | 136 | 143 | 144 | 148 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-kotlin/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-kotlin/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-kotlin/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-kotlin/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-kotlin/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-kotlin/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-kotlin/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-kotlin/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #DB2659 4 | #DB2659 5 | #FEC63B 6 | 7 | #000000 8 | #FEC63B 9 | 10 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #F7F7F7 4 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Canvas Editor 3 | 4 | -------------------------------------------------------------------------------- /example-kotlin/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /example-kotlin/app/src/test/java/com/outsbook/examplekotlin/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.outsbook.examplekotlin 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 | -------------------------------------------------------------------------------- /example-kotlin/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.72' 5 | repositories { 6 | google() 7 | jcenter() 8 | 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.6.3' 12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 13 | 14 | // NOTE: Do not place your application dependencies here; they belong 15 | // in the individual module build.gradle files 16 | } 17 | } 18 | 19 | allprojects { 20 | repositories { 21 | google() 22 | jcenter() 23 | 24 | } 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /example-kotlin/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 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | -------------------------------------------------------------------------------- /example-kotlin/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/example-kotlin/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /example-kotlin/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Apr 30 23:07:38 BDT 2020 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.6.4-all.zip 7 | -------------------------------------------------------------------------------- /example-kotlin/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 | -------------------------------------------------------------------------------- /example-kotlin/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 | -------------------------------------------------------------------------------- /example-kotlin/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name='ExampleKotlin' 2 | include ':app' 3 | -------------------------------------------------------------------------------- /screenshot/screenshot_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/screenshot/screenshot_1.png -------------------------------------------------------------------------------- /screenshot/screenshot_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/screenshot/screenshot_2.png -------------------------------------------------------------------------------- /screenshot/screenshot_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/screenshot/screenshot_3.png -------------------------------------------------------------------------------- /screenshot/screenshot_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/screenshot/screenshot_4.png -------------------------------------------------------------------------------- /screenshot/screenshot_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/screenshot/screenshot_5.png -------------------------------------------------------------------------------- /screenshot/screenshot_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/screenshot/screenshot_6.png -------------------------------------------------------------------------------- /screenshot/screenshot_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outsbook/CanvasEditor/db102fb1ee36ca4e23fb657354f502033cccde02/screenshot/screenshot_7.png --------------------------------------------------------------------------------