├── image
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── image_ic_ok.png
│ │ │ │ ├── image_ic_adjust.png
│ │ │ │ ├── image_ic_cancel.png
│ │ │ │ ├── image_ic_clip.png
│ │ │ │ ├── image_ic_delete.png
│ │ │ │ ├── image_ic_doodle.png
│ │ │ │ ├── image_ic_mosaic.png
│ │ │ │ ├── image_ic_rotate.png
│ │ │ │ ├── image_ic_text.png
│ │ │ │ ├── image_ic_undo.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ ├── image_ic_ok_pressed.png
│ │ │ │ ├── image_ic_clip_checked.png
│ │ │ │ ├── image_ic_text_checked.png
│ │ │ │ ├── image_ic_undo_disable.png
│ │ │ │ ├── image_ic_cancel_pressed.png
│ │ │ │ ├── image_ic_doodle_checked.png
│ │ │ │ ├── image_ic_mosaic_checked.png
│ │ │ │ └── image_ic_rotate_pressed.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── image_ic_ok.png
│ │ │ │ ├── image_ic_clip.png
│ │ │ │ ├── image_ic_text.png
│ │ │ │ ├── image_ic_undo.png
│ │ │ │ ├── image_ic_adjust.png
│ │ │ │ ├── image_ic_cancel.png
│ │ │ │ ├── image_ic_delete.png
│ │ │ │ ├── image_ic_doodle.png
│ │ │ │ ├── image_ic_mosaic.png
│ │ │ │ ├── image_ic_rotate.png
│ │ │ │ ├── image_ic_ok_pressed.png
│ │ │ │ ├── image_ic_clip_checked.png
│ │ │ │ ├── image_ic_text_checked.png
│ │ │ │ ├── image_ic_undo_disable.png
│ │ │ │ ├── image_ic_cancel_pressed.png
│ │ │ │ ├── image_ic_doodle_checked.png
│ │ │ │ ├── image_ic_mosaic_checked.png
│ │ │ │ └── image_ic_rotate_pressed.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── image_ic_clip.png
│ │ │ │ ├── image_ic_ok.png
│ │ │ │ ├── image_ic_text.png
│ │ │ │ ├── image_ic_undo.png
│ │ │ │ ├── image_ic_adjust.png
│ │ │ │ ├── image_ic_cancel.png
│ │ │ │ ├── image_ic_delete.png
│ │ │ │ ├── image_ic_doodle.png
│ │ │ │ ├── image_ic_mosaic.png
│ │ │ │ ├── image_ic_rotate.png
│ │ │ │ ├── image_ic_ok_pressed.png
│ │ │ │ ├── image_ic_cancel_pressed.png
│ │ │ │ ├── image_ic_clip_checked.png
│ │ │ │ ├── image_ic_doodle_checked.png
│ │ │ │ ├── image_ic_mosaic_checked.png
│ │ │ │ ├── image_ic_rotate_pressed.png
│ │ │ │ ├── image_ic_text_checked.png
│ │ │ │ └── image_ic_undo_disable.png
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── image_dimens.xml
│ │ │ │ ├── image_attrs.xml
│ │ │ │ ├── image_colors.xml
│ │ │ │ ├── image_strings.xml
│ │ │ │ └── image_styles.xml
│ │ │ ├── drawable
│ │ │ │ ├── image_bg_bottom.xml
│ │ │ │ ├── image_bg_top.xml
│ │ │ │ ├── image_btn_ok.xml
│ │ │ │ ├── image_btn_undo.xml
│ │ │ │ ├── image_btn_cancel.xml
│ │ │ │ ├── image_btn_rotate.xml
│ │ │ │ ├── image_btn_clip.xml
│ │ │ │ ├── image_btn_text.xml
│ │ │ │ ├── image_btn_doodle.xml
│ │ │ │ └── image_btn_mosaic.xml
│ │ │ ├── anim
│ │ │ │ ├── image_fade_in.xml
│ │ │ │ ├── image_fade_out.xml
│ │ │ │ ├── image_dialog_exit.xml
│ │ │ │ └── image_dialog_enter.xml
│ │ │ ├── menu
│ │ │ │ └── image_menu_gallery.xml
│ │ │ ├── color
│ │ │ │ └── image_color_text.xml
│ │ │ └── layout
│ │ │ │ ├── image_layout_gallery_pop.xml
│ │ │ │ ├── image_edit_activity.xml
│ │ │ │ ├── image_layout_image.xml
│ │ │ │ ├── image_inc_gallery_request_permission.xml
│ │ │ │ ├── image_layout_gallery_menu_item.xml
│ │ │ │ ├── image_text_dialog.xml
│ │ │ │ ├── image_color_layout.xml
│ │ │ │ ├── image_gallery_activity.xml
│ │ │ │ ├── image_edit_clip_layout.xml
│ │ │ │ └── image_edit_opt_layout.xml
│ │ ├── java
│ │ │ └── me
│ │ │ │ └── minetsh
│ │ │ │ └── imaging
│ │ │ │ ├── core
│ │ │ │ ├── IMGMode.java
│ │ │ │ ├── sticker
│ │ │ │ │ ├── IMGStickerTouchHelper.java
│ │ │ │ │ ├── IMGSticker.java
│ │ │ │ │ ├── IMGStickerPortrait.java
│ │ │ │ │ ├── IMGStickerMoveHelper.java
│ │ │ │ │ ├── IMGStickerHelper.java
│ │ │ │ │ ├── IMGStickerAdjustHelper.java
│ │ │ │ │ └── IMGStickerX.java
│ │ │ │ ├── file
│ │ │ │ │ ├── IMGDecoder.java
│ │ │ │ │ ├── IMGFileDecoder.java
│ │ │ │ │ └── IMGAssetFileDecoder.java
│ │ │ │ ├── IMGViewPortrait.java
│ │ │ │ ├── elastic
│ │ │ │ │ ├── IMGPointFEvaluator.java
│ │ │ │ │ ├── IMGElasticAnimator.java
│ │ │ │ │ ├── IMGRectFEvaluator.java
│ │ │ │ │ └── IMGElastic.java
│ │ │ │ ├── util
│ │ │ │ │ └── IMGPermissionUtils.java
│ │ │ │ ├── homing
│ │ │ │ │ ├── IMGHomingEvaluator.java
│ │ │ │ │ └── IMGHoming.java
│ │ │ │ ├── IMGText.java
│ │ │ │ ├── anim
│ │ │ │ │ └── IMGHomingAnimator.java
│ │ │ │ ├── IMGPath.java
│ │ │ │ └── clip
│ │ │ │ │ ├── IMGClip.java
│ │ │ │ │ └── IMGClipWindow.java
│ │ │ │ ├── widget
│ │ │ │ ├── IMGViewHolderCallback.java
│ │ │ │ └── IMGGalleryHolderCallback.java
│ │ │ │ ├── IMG.java
│ │ │ │ ├── view
│ │ │ │ ├── IMGStickerContainer.java
│ │ │ │ ├── IMGStickerImageView.java
│ │ │ │ ├── IMGColorGroup.java
│ │ │ │ ├── IMGStickerXText.java
│ │ │ │ ├── IMGStickerTextView.java
│ │ │ │ ├── IMGColorRadio.java
│ │ │ │ └── IMGStickerView.java
│ │ │ │ ├── gallery
│ │ │ │ ├── model
│ │ │ │ │ ├── IMGImageViewModel.java
│ │ │ │ │ ├── IMGImageInfo.java
│ │ │ │ │ └── IMGChooseMode.java
│ │ │ │ ├── IMGScanTask.java
│ │ │ │ ├── IMGScanner.java
│ │ │ │ └── IMGGalleryMenuWindow.java
│ │ │ │ ├── IMGTextEditDialog.java
│ │ │ │ ├── IMGEditActivity.java
│ │ │ │ └── IMGEditBaseActivity.java
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── me
│ │ │ └── minetsh
│ │ │ └── imaging
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── me
│ │ └── minetsh
│ │ └── imaging
│ │ └── ExampleInstrumentedTest.java
├── proguard-rules.pro
└── build.gradle
├── sample
├── .gitignore
├── src
│ ├── main
│ │ ├── assets
│ │ │ ├── am.jpg
│ │ │ └── g.jpeg
│ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── sample_album.png
│ │ │ │ ├── sample_edited.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── drawable
│ │ │ │ ├── shape_image.xml
│ │ │ │ ├── fg_image.xml
│ │ │ │ └── ic_launcher_background.xml
│ │ │ ├── values-zh
│ │ │ │ └── strings.xml
│ │ │ ├── values
│ │ │ │ ├── colors.xml
│ │ │ │ ├── strings.xml
│ │ │ │ └── styles.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── layout
│ │ │ │ ├── layout_image.xml
│ │ │ │ ├── activity_image_edit_sample.xml
│ │ │ │ ├── activity_main.xml
│ │ │ │ └── activity_gallery_sample.xml
│ │ │ └── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ ├── java
│ │ │ └── me
│ │ │ │ └── minetsh
│ │ │ │ └── imaging
│ │ │ │ └── sample
│ │ │ │ ├── CustomGalleryActivity.kt
│ │ │ │ ├── MainApplication.kt
│ │ │ │ ├── MainActivity.kt
│ │ │ │ ├── ImageEditSampleActivity.kt
│ │ │ │ └── GallerySampleActivity.kt
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── me
│ │ │ └── minetsh
│ │ │ └── imaging
│ │ │ └── sample
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── me
│ │ └── minetsh
│ │ └── imaging
│ │ └── sample
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── screenshot
├── pv.webp
├── summary.png
└── wechat.jpeg
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .idea
├── caches
│ └── build_file_checksums.ser
├── vcs.xml
├── modules.xml
├── runConfigurations.xml
├── gradle.xml
└── misc.xml
├── .gitignore
├── gradle.properties
├── README.md
├── gradlew.bat
└── gradlew
/image/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/sample/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':sample', ':image'
2 |
--------------------------------------------------------------------------------
/screenshot/pv.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/screenshot/pv.webp
--------------------------------------------------------------------------------
/screenshot/summary.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/screenshot/summary.png
--------------------------------------------------------------------------------
/screenshot/wechat.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/screenshot/wechat.jpeg
--------------------------------------------------------------------------------
/sample/src/main/assets/am.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/sample/src/main/assets/am.jpg
--------------------------------------------------------------------------------
/sample/src/main/assets/g.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/sample/src/main/assets/g.jpeg
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/.idea/caches/build_file_checksums.ser:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/.idea/caches/build_file_checksums.ser
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xhdpi/image_ic_ok.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xhdpi/image_ic_ok.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxhdpi/image_ic_ok.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxhdpi/image_ic_ok.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/sample/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/sample/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/sample/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xhdpi/image_ic_adjust.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xhdpi/image_ic_adjust.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xhdpi/image_ic_cancel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xhdpi/image_ic_cancel.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xhdpi/image_ic_clip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xhdpi/image_ic_clip.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xhdpi/image_ic_delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xhdpi/image_ic_delete.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xhdpi/image_ic_doodle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xhdpi/image_ic_doodle.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xhdpi/image_ic_mosaic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xhdpi/image_ic_mosaic.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xhdpi/image_ic_rotate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xhdpi/image_ic_rotate.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xhdpi/image_ic_text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xhdpi/image_ic_text.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xhdpi/image_ic_undo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xhdpi/image_ic_undo.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxhdpi/image_ic_clip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxhdpi/image_ic_clip.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxhdpi/image_ic_text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxhdpi/image_ic_text.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxhdpi/image_ic_undo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxhdpi/image_ic_undo.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxxhdpi/image_ic_clip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxxhdpi/image_ic_clip.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxxhdpi/image_ic_ok.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxxhdpi/image_ic_ok.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxxhdpi/image_ic_text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxxhdpi/image_ic_text.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxxhdpi/image_ic_undo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxxhdpi/image_ic_undo.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xhdpi/sample_album.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/sample/src/main/res/mipmap-xhdpi/sample_album.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xhdpi/sample_edited.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/sample/src/main/res/mipmap-xhdpi/sample_edited.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxhdpi/image_ic_adjust.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxhdpi/image_ic_adjust.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxhdpi/image_ic_cancel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxhdpi/image_ic_cancel.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxhdpi/image_ic_delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxhdpi/image_ic_delete.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxhdpi/image_ic_doodle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxhdpi/image_ic_doodle.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxhdpi/image_ic_mosaic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxhdpi/image_ic_mosaic.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxhdpi/image_ic_rotate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxhdpi/image_ic_rotate.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxxhdpi/image_ic_adjust.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxxhdpi/image_ic_adjust.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxxhdpi/image_ic_cancel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxxhdpi/image_ic_cancel.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxxhdpi/image_ic_delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxxhdpi/image_ic_delete.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxxhdpi/image_ic_doodle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxxhdpi/image_ic_doodle.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxxhdpi/image_ic_mosaic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxxhdpi/image_ic_mosaic.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxxhdpi/image_ic_rotate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxxhdpi/image_ic_rotate.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xhdpi/image_ic_ok_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xhdpi/image_ic_ok_pressed.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxhdpi/image_ic_ok_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxhdpi/image_ic_ok_pressed.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xhdpi/image_ic_clip_checked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xhdpi/image_ic_clip_checked.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xhdpi/image_ic_text_checked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xhdpi/image_ic_text_checked.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xhdpi/image_ic_undo_disable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xhdpi/image_ic_undo_disable.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxhdpi/image_ic_clip_checked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxhdpi/image_ic_clip_checked.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxhdpi/image_ic_text_checked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxhdpi/image_ic_text_checked.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxhdpi/image_ic_undo_disable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxhdpi/image_ic_undo_disable.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxxhdpi/image_ic_ok_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxxhdpi/image_ic_ok_pressed.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xhdpi/image_ic_cancel_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xhdpi/image_ic_cancel_pressed.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xhdpi/image_ic_doodle_checked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xhdpi/image_ic_doodle_checked.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xhdpi/image_ic_mosaic_checked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xhdpi/image_ic_mosaic_checked.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xhdpi/image_ic_rotate_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xhdpi/image_ic_rotate_pressed.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxhdpi/image_ic_cancel_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxhdpi/image_ic_cancel_pressed.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxhdpi/image_ic_doodle_checked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxhdpi/image_ic_doodle_checked.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxhdpi/image_ic_mosaic_checked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxhdpi/image_ic_mosaic_checked.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxhdpi/image_ic_rotate_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxhdpi/image_ic_rotate_pressed.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxxhdpi/image_ic_cancel_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxxhdpi/image_ic_cancel_pressed.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxxhdpi/image_ic_clip_checked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxxhdpi/image_ic_clip_checked.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxxhdpi/image_ic_doodle_checked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxxhdpi/image_ic_doodle_checked.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxxhdpi/image_ic_mosaic_checked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxxhdpi/image_ic_mosaic_checked.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxxhdpi/image_ic_rotate_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxxhdpi/image_ic_rotate_pressed.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxxhdpi/image_ic_text_checked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxxhdpi/image_ic_text_checked.png
--------------------------------------------------------------------------------
/image/src/main/res/mipmap-xxxhdpi/image_ic_undo_disable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minetsh/Imaging/HEAD/image/src/main/res/mipmap-xxxhdpi/image_ic_undo_disable.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/shape_image.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/sample/src/main/res/values-zh/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 选择图片
5 | 9
6 | 多选
7 |
8 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/core/IMGMode.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.core;
2 |
3 | /**
4 | * Created by felix on 2017/11/22 下午6:18.
5 | */
6 |
7 | public enum IMGMode {
8 |
9 | NONE,
10 | DOODLE,
11 | MOSAIC,
12 | CLIP
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/core/sticker/IMGStickerTouchHelper.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.core.sticker;
2 |
3 | /**
4 | * Created by felix on 2017/12/5 下午12:25.
5 | */
6 |
7 | public class IMGStickerTouchHelper {
8 |
9 | // TODO
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/image/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 开启存储权限
5 |
6 | 相册功能需要使用到存储权限,请在设置中开启此应用的存储权限
7 |
8 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Nov 14 11:14:25 CST 2017
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 |
--------------------------------------------------------------------------------
/image/src/main/res/values/image_dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 28dp
5 |
6 | 6dp
7 |
8 | 32dp
9 |
10 |
--------------------------------------------------------------------------------
/image/src/main/res/drawable/image_bg_bottom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
--------------------------------------------------------------------------------
/image/src/main/res/drawable/image_bg_top.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
--------------------------------------------------------------------------------
/image/src/main/res/drawable/image_btn_ok.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/core/sticker/IMGSticker.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.core.sticker;
2 |
3 | import me.minetsh.imaging.core.IMGViewPortrait;
4 |
5 | /**
6 | * Created by felix on 2017/11/14 下午7:31.
7 | */
8 |
9 | public interface IMGSticker extends IMGStickerPortrait, IMGViewPortrait {
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/image/src/main/res/drawable/image_btn_undo.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/image/src/main/res/drawable/image_btn_cancel.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/image/src/main/res/drawable/image_btn_rotate.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/fg_image.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/image/src/main/res/anim/image_fade_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/image/src/main/res/anim/image_fade_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/widget/IMGViewHolderCallback.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.widget;
2 |
3 | import androidx.recyclerview.widget.RecyclerView;
4 |
5 | /**
6 | * Created by felix on 2018/1/4 下午3:52.
7 | */
8 |
9 | public interface IMGViewHolderCallback {
10 |
11 | void onViewHolderClick(RecyclerView.ViewHolder holder);
12 | }
13 |
--------------------------------------------------------------------------------
/sample/src/main/java/me/minetsh/imaging/sample/CustomGalleryActivity.kt:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.sample
2 |
3 | import me.minetsh.imaging.IMGGalleryActivity
4 |
5 | /**
6 | * Created by felix on 2018/1/5 下午1:25.
7 | */
8 |
9 | class CustomGalleryActivity : IMGGalleryActivity() {
10 |
11 | // 使用时可不必继承,直接在AndroidManifest 中添加IMGGalleryActivity 并修改其主题即可
12 |
13 | }
--------------------------------------------------------------------------------
/image/src/main/res/menu/image_menu_gallery.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/widget/IMGGalleryHolderCallback.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.widget;
2 |
3 | import androidx.recyclerview.widget.RecyclerView;
4 |
5 | /**
6 | * Created by felix on 2018/1/4 下午3:53.
7 | */
8 |
9 | public interface IMGGalleryHolderCallback extends IMGViewHolderCallback {
10 |
11 | void onCheckClick(RecyclerView.ViewHolder holder);
12 | }
13 |
--------------------------------------------------------------------------------
/image/src/main/res/color/image_color_text.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/image/src/main/res/drawable/image_btn_clip.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/image/src/main/res/drawable/image_btn_text.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/image/src/main/res/drawable/image_btn_doodle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/image/src/main/res/drawable/image_btn_mosaic.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/sample/src/main/java/me/minetsh/imaging/sample/MainApplication.kt:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.sample
2 |
3 | import android.app.Application
4 | import com.facebook.drawee.backends.pipeline.Fresco
5 |
6 | /**
7 | * Created by felix on 2018/1/5 下午12:34.
8 | */
9 |
10 | class MainApplication : Application() {
11 |
12 | override fun onCreate() {
13 | super.onCreate()
14 | Fresco.initialize(applicationContext)
15 | }
16 | }
--------------------------------------------------------------------------------
/sample/src/test/java/me/minetsh/imaging/sample/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.sample
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 |
--------------------------------------------------------------------------------
/image/src/main/res/anim/image_dialog_exit.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
14 |
15 |
--------------------------------------------------------------------------------
/image/src/main/res/anim/image_dialog_enter.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
14 |
15 |
--------------------------------------------------------------------------------
/image/src/test/java/me/minetsh/imaging/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging;
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() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/sample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Imaging
4 | 编辑
5 | 单选
6 | 选择图片
7 | 9
8 | 多选
9 |
10 | 图片编辑
11 | 相册
12 | 自定义相册
13 |
14 |
15 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/IMG.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging;
2 |
3 | import android.content.Context;
4 |
5 | /**
6 | * Created by felix on 2017/12/26 下午1:34.
7 | */
8 |
9 | public class IMG {
10 |
11 | private static Context mApplicationContext;
12 |
13 | public static void initialize(Context context) {
14 | mApplicationContext = context.getApplicationContext();
15 |
16 | }
17 |
18 | public static class Config {
19 |
20 | private boolean isSave;
21 |
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/image/src/main/res/values/image_attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/image/src/main/res/values/image_colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #FFFFFF
5 | #4CA1FB
6 |
7 | #FFFFFF
8 | #2B2B2B
9 | #FF5151
10 | #20FFCD
11 | #FFCC00
12 | #0078FF
13 |
14 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/layout_image.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
--------------------------------------------------------------------------------
/image/src/main/res/values/image_strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 涂鸦
4 | 文字
5 | 马赛克
6 | 裁剪
7 | 还原
8 | 完成
9 | 取消
10 | 旋转
11 |
12 | 请直接在图片上涂抹虚化
13 | 撤销
14 | 所有照片
15 | 原图
16 | 预览
17 |
18 |
--------------------------------------------------------------------------------
/image/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
12 |
13 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/core/file/IMGDecoder.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.core.file;
2 |
3 | import android.graphics.Bitmap;
4 | import android.graphics.BitmapFactory;
5 | import android.net.Uri;
6 |
7 | /**
8 | * Created by felix on 2017/12/26 下午2:54.
9 | */
10 |
11 | public abstract class IMGDecoder {
12 |
13 | private Uri uri;
14 |
15 | public IMGDecoder(Uri uri) {
16 | this.uri = uri;
17 | }
18 |
19 | public Uri getUri() {
20 | return uri;
21 | }
22 |
23 | public void setUri(Uri uri) {
24 | this.uri = uri;
25 | }
26 |
27 | public Bitmap decode() {
28 | return decode(null);
29 | }
30 |
31 | public abstract Bitmap decode(BitmapFactory.Options options);
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/sample/src/androidTest/java/me/minetsh/imaging/sample/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.sample
2 |
3 | import androidx.test.InstrumentationRegistry
4 | import androidx.test.runner.AndroidJUnit4
5 | import org.junit.Assert.assertEquals
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | /**
10 | * Instrumented test, which will execute on an Android device.
11 | *
12 | * See [testing documentation](http://d.android.com/tools/testing).
13 | */
14 | @RunWith(AndroidJUnit4::class)
15 | class ExampleInstrumentedTest {
16 | @Test
17 | fun useAppContext() {
18 | // Context of the app under test.
19 | val appContext = InstrumentationRegistry.getTargetContext()
20 | assertEquals("me.minetsh.imaging.sample", appContext.packageName)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/core/IMGViewPortrait.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.core;
2 |
3 | /**
4 | * Created by felix on 2017/11/16 下午5:49.
5 | */
6 |
7 | public interface IMGViewPortrait {
8 |
9 | int getWidth();
10 |
11 | int getHeight();
12 |
13 | float getScaleX();
14 |
15 | float getScaleY();
16 |
17 | float getRotation();
18 |
19 | float getPivotX();
20 |
21 | float getPivotY();
22 |
23 | float getX();
24 |
25 | float getY();
26 |
27 | void setX(float x);
28 |
29 | void setY(float y);
30 |
31 | void setRotation(float rotate);
32 |
33 | void setScaleX(float scaleX);
34 |
35 | void setScaleY(float scaleY);
36 |
37 | float getScale();
38 |
39 | void setScale(float scale);
40 |
41 | void addScale(float scale);
42 | }
43 |
--------------------------------------------------------------------------------
/image/src/main/res/layout/image_layout_gallery_pop.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
18 |
19 |
--------------------------------------------------------------------------------
/image/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the setSize 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 |
--------------------------------------------------------------------------------
/sample/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the setSize 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 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/image/src/androidTest/java/me/minetsh/imaging/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.test.InstrumentationRegistry;
6 | import androidx.test.runner.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | import static org.junit.Assert.assertEquals;
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() throws Exception {
22 | // Context of the app under test.
23 | Context appContext = InstrumentationRegistry.getTargetContext();
24 |
25 | assertEquals("me.minetsh.imaging.test", appContext.getPackageName());
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/image/src/main/res/layout/image_edit_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8
13 | org.gradle.parallel=true
14 | org.gradle.configureondemand=true
15 |
16 | # When configured, Gradle will run in incubating parallel mode.
17 | # This option should only be used with decoupled projects. More details, visit
18 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
19 | android.useAndroidX=true
20 | android.enableJetifier=true
21 | android.injected.testOnly=false
22 |
--------------------------------------------------------------------------------
/sample/src/main/java/me/minetsh/imaging/sample/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.sample
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import androidx.appcompat.app.AppCompatActivity
6 | import kotlinx.android.synthetic.main.activity_main.*
7 |
8 | const val REQ_IMAGE_EDIT = 1
9 |
10 | const val REQ_IMAGE_CHOOSE = 2
11 |
12 | class MainActivity : AppCompatActivity() {
13 |
14 | override fun onCreate(savedInstanceState: Bundle?) {
15 | super.onCreate(savedInstanceState)
16 | setContentView(R.layout.activity_main)
17 |
18 | sdv_image_edit.setOnClickListener {
19 | startActivity(Intent(
20 | this, ImageEditSampleActivity::class.java
21 | ))
22 | }
23 |
24 | sdv_image_album.setOnClickListener {
25 | startActivity(Intent(
26 | this, GallerySampleActivity::class.java
27 | ))
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/core/elastic/IMGPointFEvaluator.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.core.elastic;
2 |
3 | import android.animation.TypeEvaluator;
4 | import android.graphics.PointF;
5 |
6 | /**
7 | * Created by felix on 2017/11/27 下午6:27.
8 | */
9 |
10 | public class IMGPointFEvaluator implements TypeEvaluator {
11 |
12 | private PointF mPoint;
13 |
14 | public IMGPointFEvaluator() {
15 |
16 | }
17 |
18 | public IMGPointFEvaluator(PointF reuse) {
19 | mPoint = reuse;
20 | }
21 |
22 | @Override
23 | public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
24 | float x = startValue.x + (fraction * (endValue.x - startValue.x));
25 | float y = startValue.y + (fraction * (endValue.y - startValue.y));
26 |
27 | if (mPoint != null) {
28 | mPoint.set(x, y);
29 | return mPoint;
30 | } else {
31 | return new PointF(x, y);
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/image/src/main/res/layout/image_layout_image.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
24 |
25 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/core/file/IMGFileDecoder.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.core.file;
2 |
3 | import android.graphics.Bitmap;
4 | import android.graphics.BitmapFactory;
5 | import android.net.Uri;
6 | import android.text.TextUtils;
7 |
8 | import java.io.File;
9 |
10 | /**
11 | * Created by felix on 2017/12/26 下午3:07.
12 | */
13 |
14 | public class IMGFileDecoder extends IMGDecoder {
15 |
16 | public IMGFileDecoder(Uri uri) {
17 | super(uri);
18 | }
19 |
20 | @Override
21 | public Bitmap decode(BitmapFactory.Options options) {
22 | Uri uri = getUri();
23 | if (uri == null) {
24 | return null;
25 | }
26 |
27 | String path = uri.getPath();
28 | if (TextUtils.isEmpty(path)) {
29 | return null;
30 | }
31 |
32 | File file = new File(path);
33 | if (file.exists()) {
34 | return BitmapFactory.decodeFile(path, options);
35 | }
36 |
37 | return null;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/core/sticker/IMGStickerPortrait.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.core.sticker;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.RectF;
5 | import android.view.View;
6 |
7 | /**
8 | * Created by felix on 2017/11/16 下午5:54.
9 | */
10 |
11 | public interface IMGStickerPortrait {
12 |
13 | boolean show();
14 |
15 | boolean remove();
16 |
17 | boolean dismiss();
18 |
19 | boolean isShowing();
20 |
21 | RectF getFrame();
22 |
23 | // RectF getAdjustFrame();
24 | //
25 | // RectF getDeleteFrame();
26 |
27 | void onSticker(Canvas canvas);
28 |
29 | void registerCallback(IMGSticker.Callback callback);
30 |
31 | void unregisterCallback(IMGSticker.Callback callback);
32 |
33 | interface Callback {
34 |
35 | void onDismiss(V stickerView);
36 |
37 | void onShowing(V stickerView);
38 |
39 | boolean onRemove(V stickerView);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/view/IMGStickerContainer.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.view;
2 |
3 | import android.content.Context;
4 | import android.view.View;
5 | import android.view.ViewGroup;
6 |
7 | /**
8 | * Created by felix on 2017/12/21 下午2:28.
9 | */
10 |
11 | public class IMGStickerContainer extends ViewGroup {
12 |
13 | public IMGStickerContainer(Context context) {
14 | super(context);
15 | }
16 |
17 | @Override
18 | protected void onLayout(boolean changed, int l, int t, int r, int b) {
19 | if (getChildCount() == 1) {
20 | View child = getChildAt(0);
21 | if (child != null) {
22 | child.layout(
23 | (l + r) >> 1 - (child.getMeasuredWidth() >> 1),
24 | (t + b) >> 1 - (child.getMeasuredHeight() >> 1),
25 | (l + r) >> 1 + (child.getMeasuredWidth() >> 1),
26 | (t + b) >> 1 + (child.getMeasuredHeight() >> 1)
27 | );
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/image/src/main/res/layout/image_inc_gallery_request_permission.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
17 |
18 |
25 |
26 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/view/IMGStickerImageView.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.view;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.View;
6 | import android.widget.ImageView;
7 |
8 | import me.minetsh.imaging.R;
9 |
10 | /**
11 | * Created by felix on 2017/12/21 下午10:58.
12 | */
13 |
14 | public class IMGStickerImageView extends IMGStickerView {
15 |
16 | private ImageView mImageView;
17 |
18 | public IMGStickerImageView(Context context) {
19 | super(context);
20 | }
21 |
22 | public IMGStickerImageView(Context context, AttributeSet attrs) {
23 | super(context, attrs);
24 | }
25 |
26 | public IMGStickerImageView(Context context, AttributeSet attrs, int defStyleAttr) {
27 | super(context, attrs, defStyleAttr);
28 | }
29 |
30 | @Override
31 | public View onCreateContentView(Context context) {
32 | mImageView = new ImageView(context);
33 | mImageView.setImageResource(R.mipmap.ic_launcher);
34 | return mImageView;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/core/elastic/IMGElasticAnimator.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.core.elastic;
2 |
3 | import android.animation.ValueAnimator;
4 | import android.graphics.PointF;
5 | import android.view.animation.AccelerateDecelerateInterpolator;
6 |
7 | /**
8 | * Created by felix on 2017/11/27 下午5:22.
9 | */
10 |
11 | public class IMGElasticAnimator extends ValueAnimator {
12 |
13 | private IMGElastic mElastic;
14 |
15 | public IMGElasticAnimator() {
16 | setEvaluator(new IMGPointFEvaluator());
17 | setInterpolator(new AccelerateDecelerateInterpolator());
18 | }
19 |
20 | public IMGElasticAnimator(IMGElastic elastic) {
21 | this();
22 | setElastic(elastic);
23 | }
24 |
25 | public void setElastic(IMGElastic elastic) {
26 | mElastic = elastic;
27 |
28 | if (mElastic == null) {
29 | throw new IllegalArgumentException("IMGElastic cannot be null.");
30 | }
31 | }
32 |
33 | public void start(float x, float y) {
34 | setObjectValues(new PointF(x, y), mElastic.getPivot());
35 | start();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/core/util/IMGPermissionUtils.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.core.util;
2 |
3 | import android.content.Context;
4 | import android.content.pm.PackageManager;
5 |
6 | import androidx.core.app.ActivityCompat;
7 |
8 | public class IMGPermissionUtils {
9 |
10 | private IMGPermissionUtils() {
11 |
12 | }
13 |
14 | public static boolean isPermissionGranted(Context context, String permission) {
15 | return checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED;
16 | }
17 |
18 | public static boolean isPermissionsGranted(Context context, String[] permissions) {
19 | for (String permission : permissions) {
20 | if (ActivityCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
21 | return false;
22 | }
23 | }
24 | return true;
25 | }
26 |
27 | public static int checkSelfPermission(Context context, String permission) {
28 | return ActivityCompat.checkSelfPermission(context, permission);
29 | }
30 |
31 | public static void requestPermissions() {
32 |
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/core/homing/IMGHomingEvaluator.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.core.homing;
2 |
3 | import android.animation.TypeEvaluator;
4 |
5 | /**
6 | * Created by felix on 2017/11/28 下午4:13.
7 | */
8 |
9 | public class IMGHomingEvaluator implements TypeEvaluator {
10 |
11 | private IMGHoming homing;
12 |
13 | public IMGHomingEvaluator() {
14 |
15 | }
16 |
17 | public IMGHomingEvaluator(IMGHoming homing) {
18 | this.homing = homing;
19 | }
20 |
21 | @Override
22 | public IMGHoming evaluate(float fraction, IMGHoming startValue, IMGHoming endValue) {
23 | float x = startValue.x + fraction * (endValue.x - startValue.x);
24 | float y = startValue.y + fraction * (endValue.y - startValue.y);
25 | float scale = startValue.scale + fraction * (endValue.scale - startValue.scale);
26 | float rotate = startValue.rotate + fraction * (endValue.rotate - startValue.rotate);
27 |
28 | if (homing == null) {
29 | homing = new IMGHoming(x, y, scale, rotate);
30 | } else {
31 | homing.set(x, y, scale, rotate);
32 | }
33 |
34 | return homing;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/core/IMGText.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.core;
2 |
3 | import android.graphics.Color;
4 | import android.text.TextUtils;
5 |
6 | /**
7 | * Created by felix on 2017/12/1 下午2:43.
8 | */
9 |
10 | public class IMGText {
11 |
12 | private String text;
13 |
14 | private int color = Color.WHITE;
15 |
16 | public IMGText(String text, int color) {
17 | this.text = text;
18 | this.color = color;
19 | }
20 |
21 | public String getText() {
22 | return text;
23 | }
24 |
25 | public void setText(String text) {
26 | this.text = text;
27 | }
28 |
29 | public int getColor() {
30 | return color;
31 | }
32 |
33 | public void setColor(int color) {
34 | this.color = color;
35 | }
36 |
37 | public boolean isEmpty() {
38 | return TextUtils.isEmpty(text);
39 | }
40 |
41 | public int length() {
42 | return isEmpty() ? 0 : text.length();
43 | }
44 |
45 | @Override
46 | public String toString() {
47 | return "IMGText{" +
48 | "text='" + text + '\'' +
49 | ", color=" + color +
50 | '}';
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/core/elastic/IMGRectFEvaluator.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.core.elastic;
2 |
3 | import android.animation.TypeEvaluator;
4 | import android.graphics.RectF;
5 |
6 | /**
7 | * Created by felix on 2017/11/27 下午6:11.
8 | */
9 |
10 | public class IMGRectFEvaluator implements TypeEvaluator {
11 |
12 | private RectF mRect;
13 |
14 | public IMGRectFEvaluator() {
15 |
16 | }
17 |
18 | public IMGRectFEvaluator(RectF reuseRect) {
19 | mRect = reuseRect;
20 | }
21 |
22 | @Override
23 | public RectF evaluate(float fraction, RectF startValue, RectF endValue) {
24 | float left = startValue.left + (endValue.left - startValue.left) * fraction;
25 | float top = startValue.top + (endValue.top - startValue.top) * fraction;
26 | float right = startValue.right + (endValue.right - startValue.right) * fraction;
27 | float bottom = startValue.bottom + (endValue.bottom - startValue.bottom) * fraction;
28 | if (mRect == null) {
29 | return new RectF(left, top, right, bottom);
30 | } else {
31 | mRect.set(left, top, right, bottom);
32 | return mRect;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/view/IMGColorGroup.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.view;
2 |
3 | import android.content.Context;
4 | import android.graphics.Color;
5 | import android.util.AttributeSet;
6 | import android.widget.RadioGroup;
7 |
8 | /**
9 | * Created by felix on 2017/12/1 下午3:07.
10 | */
11 |
12 | public class IMGColorGroup extends RadioGroup {
13 |
14 | public IMGColorGroup(Context context) {
15 | super(context);
16 | }
17 |
18 | public IMGColorGroup(Context context, AttributeSet attrs) {
19 | super(context, attrs);
20 | }
21 |
22 | public int getCheckColor() {
23 | int checkedId = getCheckedRadioButtonId();
24 | IMGColorRadio radio = findViewById(checkedId);
25 | if (radio != null) {
26 | return radio.getColor();
27 | }
28 | return Color.WHITE;
29 | }
30 |
31 | public void setCheckColor(int color) {
32 | int count = getChildCount();
33 | for (int i = 0; i < count; i++) {
34 | IMGColorRadio radio = (IMGColorRadio) getChildAt(i);
35 | if (radio.getColor() == color) {
36 | radio.setChecked(true);
37 | break;
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/core/anim/IMGHomingAnimator.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.core.anim;
2 |
3 | import android.animation.ValueAnimator;
4 | import android.view.animation.AccelerateDecelerateInterpolator;
5 |
6 | import me.minetsh.imaging.core.homing.IMGHoming;
7 | import me.minetsh.imaging.core.homing.IMGHomingEvaluator;
8 |
9 | /**
10 | * Created by felix on 2017/11/28 下午12:54.
11 | */
12 |
13 | public class IMGHomingAnimator extends ValueAnimator {
14 |
15 | private boolean isRotate = false;
16 |
17 | private IMGHomingEvaluator mEvaluator;
18 |
19 | public IMGHomingAnimator() {
20 | setInterpolator(new AccelerateDecelerateInterpolator());
21 | }
22 |
23 | @Override
24 | public void setObjectValues(Object... values) {
25 | super.setObjectValues(values);
26 | if (mEvaluator == null) {
27 | mEvaluator = new IMGHomingEvaluator();
28 | }
29 | setEvaluator(mEvaluator);
30 | }
31 |
32 | public void setHomingValues(IMGHoming sHoming, IMGHoming eHoming) {
33 | setObjectValues(sHoming, eHoming);
34 | isRotate = IMGHoming.isRotate(sHoming, eHoming);
35 | }
36 |
37 | public boolean isRotate() {
38 | return isRotate;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Imaging
2 |
3 | 一款图片编辑类库,功能类似微信中图片编辑功能,其主要目标用于聊天中快速编辑图片。
4 |
5 | 
6 |
7 | 包含以下主要功能:
8 |
9 | - 缩放
10 | - 涂鸦
11 | - 文字
12 | - 马赛克
13 | - 裁剪
14 | - 旋转
15 |
16 | # Usage
17 |
18 | ``` gradle
19 | implementation project(':image')
20 | ```
21 |
22 | 参考Sample示例
23 |
24 | # Sample
25 |
26 | ``` kotlin
27 | fun onChooseImages(uri: Uri, saveToPath: String) {
28 | startActivityForResult(
29 | Intent(this, IMGEditActivity::class.java)
30 | .putExtra(IMGEditActivity.EXTRA_IMAGE_URI, image.uri)
31 | .putExtra(IMGEditActivity.EXTRA_IMAGE_SAVE_PATH, saveToPath),
32 | REQ_IMAGE_EDIT
33 | )
34 | }
35 |
36 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
37 | when (requestCode) {
38 | REQ_IMAGE_EDIT -> {
39 | if (resultCode == Activity.RESULT_OK) {
40 | onImageEditDone()
41 | }
42 | }
43 | }
44 | }
45 |
46 | fun onImageEditDone() {
47 | // TODO do some thins
48 | }
49 | ```
50 |
51 | # Blog
52 |
53 | - [Android 图片编辑的原理与实现——涂鸦与马赛克](https://mp.weixin.qq.com/s/rIGGSFua-HPmUL4citYxow)
54 |
55 | # About
56 |
57 | 我的微信
58 |
--------------------------------------------------------------------------------
/sample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/core/file/IMGAssetFileDecoder.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.core.file;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.graphics.BitmapFactory;
6 | import android.net.Uri;
7 | import android.text.TextUtils;
8 |
9 | import java.io.IOException;
10 | import java.io.InputStream;
11 |
12 | /**
13 | * Created by felix on 2017/12/26 下午2:57.
14 | */
15 |
16 | public class IMGAssetFileDecoder extends IMGDecoder {
17 |
18 | private Context mContext;
19 |
20 | public IMGAssetFileDecoder(Context context, Uri uri) {
21 | super(uri);
22 | mContext = context;
23 | }
24 |
25 | public Bitmap decode(BitmapFactory.Options options) {
26 | Uri uri = getUri();
27 | if (uri == null) {
28 | return null;
29 | }
30 |
31 | String path = uri.getPath();
32 | if (TextUtils.isEmpty(path)) {
33 | return null;
34 | }
35 |
36 | path = path.substring(1);
37 |
38 | try {
39 | InputStream iStream = mContext.getAssets().open(path);
40 | return BitmapFactory.decodeStream(iStream, null, options);
41 | } catch (IOException ignore) {
42 |
43 | }
44 |
45 | return null;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/image/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 29
5 |
6 | resourcePrefix "image_"
7 |
8 | defaultConfig {
9 | minSdkVersion 21
10 | targetSdkVersion 29
11 | versionCode 1
12 | versionName "1.0"
13 |
14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
15 |
16 | }
17 |
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 |
25 | }
26 |
27 | dependencies {
28 | implementation fileTree(include: ['*.jar'], dir: 'libs')
29 | testImplementation 'junit:junit:4.12'
30 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
31 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
32 |
33 | implementation "androidx.appcompat:appcompat:1.1.0"
34 | implementation "androidx.recyclerview:recyclerview:1.1.0"
35 |
36 | implementation 'com.facebook.fresco:fresco:1.8.1'
37 | // compile 'com.facebook.fresco:animated-gif:1.7.1'
38 | //
39 | // compile 'com.facebook.fresco:animated-webp:1.7.1'
40 | // compile 'com.facebook.fresco:webpsupport:1.7.1'
41 | }
42 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/core/sticker/IMGStickerMoveHelper.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.core.sticker;
2 |
3 | import android.graphics.Matrix;
4 | import android.view.MotionEvent;
5 | import android.view.View;
6 |
7 | /**
8 | * Created by felix on 2017/11/17 下午6:08.
9 | */
10 |
11 | public class IMGStickerMoveHelper {
12 |
13 | private static final String TAG = "IMGStickerMoveHelper";
14 |
15 | private View mView;
16 |
17 | private float mX, mY;
18 |
19 | private static final Matrix M = new Matrix();
20 |
21 | public IMGStickerMoveHelper(View view) {
22 | mView = view;
23 | }
24 |
25 | public boolean onTouch(View v, MotionEvent event) {
26 | switch (event.getActionMasked()) {
27 | case MotionEvent.ACTION_DOWN:
28 | mX = event.getX();
29 | mY = event.getY();
30 | M.reset();
31 | M.setRotate(v.getRotation());
32 | return true;
33 | case MotionEvent.ACTION_MOVE:
34 | float[] dxy = {event.getX() - mX, event.getY() - mY};
35 | M.mapPoints(dxy);
36 | v.setTranslationX(mView.getTranslationX() + dxy[0]);
37 | v.setTranslationY(mView.getTranslationY() + dxy[1]);
38 | return true;
39 | }
40 | return false;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/sample/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 |
8 | defaultConfig {
9 | applicationId "me.minetsh.imaging.sample"
10 | minSdkVersion 21
11 | targetSdkVersion 29
12 | versionCode 1
13 | versionName "1.0"
14 |
15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
16 |
17 | }
18 |
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 |
26 | }
27 |
28 | dependencies {
29 | implementation fileTree(include: ['*.jar'], dir: 'libs')
30 |
31 | implementation "androidx.appcompat:appcompat:1.1.0"
32 | implementation "androidx.constraintlayout:constraintlayout:1.1.3"
33 | implementation "androidx.recyclerview:recyclerview:1.1.0"
34 |
35 | testImplementation 'junit:junit:4.12'
36 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
37 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
38 |
39 | implementation project(':image')
40 |
41 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
42 |
43 | implementation 'com.facebook.fresco:fresco:1.8.1'
44 | }
45 |
--------------------------------------------------------------------------------
/image/src/main/res/layout/image_layout_gallery_menu_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
19 |
20 |
30 |
31 |
37 |
38 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/core/homing/IMGHoming.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.core.homing;
2 |
3 | /**
4 | * Created by felix on 2017/11/28 下午4:14.
5 | */
6 |
7 | public class IMGHoming {
8 |
9 | public float x, y;
10 |
11 | public float scale;
12 |
13 | public float rotate;
14 |
15 | public IMGHoming(float x, float y, float scale, float rotate) {
16 | this.x = x;
17 | this.y = y;
18 | this.scale = scale;
19 | this.rotate = rotate;
20 | }
21 |
22 | public void set(float x, float y, float scale, float rotate) {
23 | this.x = x;
24 | this.y = y;
25 | this.scale = scale;
26 | this.rotate = rotate;
27 | }
28 |
29 | public void concat(IMGHoming homing) {
30 | this.scale *= homing.scale;
31 | this.x += homing.x;
32 | this.y += homing.y;
33 | }
34 |
35 | public void rConcat(IMGHoming homing) {
36 | this.scale *= homing.scale;
37 | this.x -= homing.x;
38 | this.y -= homing.y;
39 | }
40 |
41 | public static boolean isRotate(IMGHoming sHoming, IMGHoming eHoming) {
42 | return Float.compare(sHoming.rotate, eHoming.rotate) != 0;
43 | }
44 |
45 | @Override
46 | public String toString() {
47 | return "IMGHoming{" +
48 | "x=" + x +
49 | ", y=" + y +
50 | ", scale=" + scale +
51 | ", rotate=" + rotate +
52 | '}';
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/gallery/model/IMGImageViewModel.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.gallery.model;
2 |
3 | import android.net.Uri;
4 |
5 | /**
6 | * Created by felix on 2017/12/26 上午11:21.
7 | */
8 |
9 | public class IMGImageViewModel {
10 |
11 | private Uri uri;
12 |
13 | private boolean isSelected;
14 |
15 | private boolean isOriginal;
16 |
17 | private int width;
18 |
19 | private int height;
20 |
21 | private long size;
22 |
23 | public IMGImageViewModel(Uri uri) {
24 | this.uri = uri;
25 | }
26 |
27 | public Uri getUri() {
28 | return uri;
29 | }
30 |
31 | public void setUri(Uri uri) {
32 | this.uri = uri;
33 | }
34 |
35 | public boolean isOriginal() {
36 | return isOriginal;
37 | }
38 |
39 | public void setOriginal(boolean original) {
40 | isOriginal = original;
41 | }
42 |
43 | public boolean isSelected() {
44 | return isSelected;
45 | }
46 |
47 | public void setSelected(boolean selected) {
48 | isSelected = selected;
49 | }
50 |
51 | public void toggleSelected() {
52 | isSelected = !isSelected;
53 | }
54 |
55 | public int getWidth() {
56 | return width;
57 | }
58 |
59 | public void setWidth(int width) {
60 | this.width = width;
61 | }
62 |
63 | public int getHeight() {
64 | return height;
65 | }
66 |
67 | public void setHeight(int height) {
68 | this.height = height;
69 | }
70 |
71 | public long getSize() {
72 | return size;
73 | }
74 |
75 | public void setSize(long size) {
76 | this.size = size;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_image_edit_sample.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
22 |
23 |
31 |
32 |
33 |
34 |
39 |
40 |
--------------------------------------------------------------------------------
/image/src/main/res/values/image_styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
14 |
33 |
34 |
40 |
41 |
45 |
46 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/view/IMGStickerXText.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.view;
2 |
3 | import android.content.res.Resources;
4 | import android.graphics.Canvas;
5 | import android.graphics.Paint;
6 | import android.text.Layout;
7 | import android.text.StaticLayout;
8 | import android.text.TextPaint;
9 | import android.util.TypedValue;
10 |
11 | import me.minetsh.imaging.core.IMGText;
12 | import me.minetsh.imaging.core.sticker.IMGStickerX;
13 |
14 | /**
15 | * Created by felix on 2017/12/11 下午2:49.
16 | */
17 |
18 | public class IMGStickerXText extends IMGStickerX {
19 |
20 | private IMGText mText;
21 |
22 | private StaticLayout mTextLayout;
23 |
24 | private TextPaint mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
25 |
26 | public IMGStickerXText(IMGText text) {
27 | // 字体大小 22sp
28 | mTextPaint.setTextSize(TypedValue.applyDimension(
29 | TypedValue.COMPLEX_UNIT_SP, 22, Resources.getSystem().getDisplayMetrics()));
30 |
31 | setText(text);
32 | }
33 |
34 | public void setText(IMGText text) {
35 | mText = text;
36 |
37 | mTextPaint.setColor(text.getColor());
38 | mTextLayout = new StaticLayout(text.getText(), mTextPaint,
39 | Math.round(Resources.getSystem().getDisplayMetrics().widthPixels * 0.8f),
40 | Layout.Alignment.ALIGN_NORMAL, 1f, 0, false);
41 |
42 | float width = 0f;
43 | for (int i = 0; i < mTextLayout.getLineCount(); i++) {
44 | width = Math.max(width, mTextLayout.getLineWidth(i));
45 | }
46 |
47 | onMeasure(width, mTextLayout.getHeight());
48 | }
49 |
50 | @Override
51 | public void onDraw(Canvas canvas) {
52 | super.onDraw(canvas);
53 | canvas.save();
54 | canvas.translate(mFrame.left, mFrame.top);
55 | mTextLayout.draw(canvas);
56 | canvas.restore();
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/gallery/IMGScanTask.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.gallery;
2 |
3 | import android.os.AsyncTask;
4 |
5 | import me.minetsh.imaging.IMGGalleryActivity;
6 | import me.minetsh.imaging.gallery.model.IMGImageViewModel;
7 |
8 | import java.lang.ref.WeakReference;
9 | import java.util.List;
10 | import java.util.Map;
11 |
12 | /**
13 | * Created by felix on 2018/1/4 下午2:26.
14 | */
15 |
16 | public class IMGScanTask extends AsyncTask, Map>> {
17 |
18 | private WeakReference mActivity;
19 |
20 | public IMGScanTask(IMGGalleryActivity activity) {
21 | this.mActivity = new WeakReference<>(activity);
22 | }
23 |
24 | @Override
25 | protected Map> doInBackground(Void... contexts) {
26 | if (mActivity != null && mActivity.get() != null) {
27 | return IMGScanner.getImages14(mActivity.get(), 64, new IMGScanner.Callback() {
28 | @Override
29 | public void onImages(List images) {
30 | publishProgress(images);
31 | }
32 | });
33 | }
34 | return null;
35 | }
36 |
37 | @Override
38 | protected void onProgressUpdate(List[] values) {
39 | if (mActivity != null) {
40 | IMGGalleryActivity activity = mActivity.get();
41 | if (activity != null) {
42 | if (values != null && values.length > 0) {
43 | activity.onQuicklyImages(values[0]);
44 | }
45 | }
46 | }
47 | }
48 |
49 | @Override
50 | protected void onPostExecute(Map> images) {
51 | if (mActivity != null) {
52 | IMGGalleryActivity activity = mActivity.get();
53 | if (activity != null) {
54 | activity.onImages(images);
55 | }
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/image/src/main/res/layout/image_text_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
25 |
26 |
36 |
37 |
38 |
39 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/core/elastic/IMGElastic.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.core.elastic;
2 |
3 | import android.graphics.PointF;
4 |
5 | /**
6 | * Created by felix on 2017/11/27 下午6:43.
7 | */
8 |
9 | public class IMGElastic {
10 |
11 | private float width, height;
12 |
13 | private PointF pivot = new PointF();
14 |
15 | public IMGElastic() {
16 |
17 | }
18 |
19 | public IMGElastic(float x, float y) {
20 | pivot.set(x, y);
21 | }
22 |
23 | public IMGElastic(float x, float y, float width, float height) {
24 | pivot.set(x, y);
25 | this.width = width;
26 | this.height = height;
27 | }
28 |
29 | public float getWidth() {
30 | return width;
31 | }
32 |
33 | public void setWidth(float width) {
34 | this.width = width;
35 | }
36 |
37 | public float getHeight() {
38 | return height;
39 | }
40 |
41 | public void setHeight(float height) {
42 | this.height = height;
43 | }
44 |
45 | public float getX() {
46 | return pivot.x;
47 | }
48 |
49 | public void setX(float x) {
50 | pivot.x = x;
51 | }
52 |
53 | public float getY() {
54 | return pivot.y;
55 | }
56 |
57 | public void setY(float y) {
58 | pivot.y = y;
59 | }
60 |
61 | public void setXY(float x, float y) {
62 | pivot.set(x, y);
63 | }
64 |
65 | public PointF getPivot() {
66 | return pivot;
67 | }
68 |
69 | public void setSize(float width, float height) {
70 | this.width = width;
71 | this.height = height;
72 | }
73 |
74 | public void set(float x, float y, float width, float height) {
75 | pivot.set(x, y);
76 | this.width = width;
77 | this.height = height;
78 | }
79 |
80 | @Override
81 | public String toString() {
82 | return "IMGElastic{" +
83 | "width=" + width +
84 | ", height=" + height +
85 | ", pivot=" + pivot +
86 | '}';
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/image/src/main/res/layout/image_color_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
19 |
20 |
25 |
26 |
31 |
32 |
37 |
38 |
43 |
44 |
49 |
50 |
--------------------------------------------------------------------------------
/image/src/main/res/layout/image_gallery_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
19 |
20 |
26 |
27 |
33 |
34 |
40 |
41 |
47 |
48 |
49 |
50 |
51 |
52 |
57 |
58 |
--------------------------------------------------------------------------------
/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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
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 Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/sample/src/main/java/me/minetsh/imaging/sample/ImageEditSampleActivity.kt:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.sample
2 |
3 | import android.app.Activity
4 | import android.content.Intent
5 | import android.net.Uri
6 | import android.os.Bundle
7 | import androidx.appcompat.app.AppCompatActivity
8 | import kotlinx.android.synthetic.main.activity_image_edit_sample.*
9 | import me.minetsh.imaging.IMGEditActivity
10 | import me.minetsh.imaging.IMGGalleryActivity
11 | import me.minetsh.imaging.gallery.model.IMGChooseMode
12 | import me.minetsh.imaging.gallery.model.IMGImageInfo
13 | import java.io.File
14 | import java.util.*
15 |
16 | /**
17 | * Created by felix on 2018/1/4 下午5:59.
18 | */
19 |
20 | class ImageEditSampleActivity : AppCompatActivity() {
21 |
22 | private var mImageFile: File? = null
23 |
24 | override fun onCreate(savedInstanceState: Bundle?) {
25 | super.onCreate(savedInstanceState)
26 | setContentView(R.layout.activity_image_edit_sample)
27 |
28 | btn_choose_image.setOnClickListener {
29 | chooseImages()
30 | }
31 | }
32 |
33 | private fun chooseImages() {
34 | startActivityForResult(
35 | IMGGalleryActivity.newIntent(this, IMGChooseMode.Builder()
36 | .setSingleChoose(true)
37 | .build()),
38 | REQ_IMAGE_CHOOSE
39 | )
40 | }
41 |
42 | private fun onChooseImages(images: List?) {
43 | val image = images?.get(0)
44 | if (image != null) {
45 |
46 | sdv_image.setImageURI(image.uri, null)
47 |
48 | mImageFile = File(cacheDir, UUID.randomUUID().toString() + ".jpg")
49 |
50 | startActivityForResult(
51 | Intent(this, IMGEditActivity::class.java)
52 | .putExtra(IMGEditActivity.EXTRA_IMAGE_URI, image.uri)
53 | .putExtra(IMGEditActivity.EXTRA_IMAGE_SAVE_PATH, mImageFile?.absolutePath),
54 | REQ_IMAGE_EDIT
55 | )
56 | }
57 | }
58 |
59 | private fun onImageEditDone() {
60 | sdv_image_edit.setImageURI(Uri.fromFile(mImageFile), null)
61 | }
62 |
63 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
64 | when (requestCode) {
65 | REQ_IMAGE_CHOOSE -> {
66 | if (resultCode == Activity.RESULT_OK) {
67 | onChooseImages(IMGGalleryActivity.getImageInfos(data))
68 | }
69 | }
70 | REQ_IMAGE_EDIT -> {
71 | if (resultCode == Activity.RESULT_OK) {
72 | onImageEditDone()
73 | }
74 | }
75 | }
76 | super.onActivityResult(requestCode, resultCode, data)
77 | }
78 | }
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/core/IMGPath.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.core;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.Color;
5 | import android.graphics.Matrix;
6 | import android.graphics.Paint;
7 | import android.graphics.Path;
8 |
9 | /**
10 | * Created by felix on 2017/11/22 下午6:13.
11 | */
12 |
13 | public class IMGPath {
14 |
15 | protected Path path;
16 |
17 | private int color = Color.RED;
18 |
19 | private float width = BASE_MOSAIC_WIDTH;
20 |
21 | private IMGMode mode = IMGMode.DOODLE;
22 |
23 | public static final float BASE_DOODLE_WIDTH = 20f;
24 |
25 | public static final float BASE_MOSAIC_WIDTH = 72f;
26 |
27 | public IMGPath() {
28 | this(new Path());
29 | }
30 |
31 | public IMGPath(Path path) {
32 | this(path, IMGMode.DOODLE);
33 | }
34 |
35 | public IMGPath(Path path, IMGMode mode) {
36 | this(path, mode, Color.RED);
37 | }
38 |
39 | public IMGPath(Path path, IMGMode mode, int color) {
40 | this(path, mode, color, BASE_MOSAIC_WIDTH);
41 | }
42 |
43 | public IMGPath(Path path, IMGMode mode, int color, float width) {
44 | this.path = path;
45 | this.mode = mode;
46 | this.color = color;
47 | this.width = width;
48 | if (mode == IMGMode.MOSAIC) {
49 | path.setFillType(Path.FillType.EVEN_ODD);
50 | }
51 | }
52 |
53 | public Path getPath() {
54 | return path;
55 | }
56 |
57 | public void setPath(Path path) {
58 | this.path = path;
59 | }
60 |
61 | public int getColor() {
62 | return color;
63 | }
64 |
65 | public void setColor(int color) {
66 | this.color = color;
67 | }
68 |
69 | public IMGMode getMode() {
70 | return mode;
71 | }
72 |
73 | public void setMode(IMGMode mode) {
74 | this.mode = mode;
75 | }
76 |
77 | public void setWidth(float width) {
78 | this.width = width;
79 | }
80 |
81 | public float getWidth() {
82 | return width;
83 | }
84 |
85 | public void onDrawDoodle(Canvas canvas, Paint paint) {
86 | if (mode == IMGMode.DOODLE) {
87 | paint.setColor(color);
88 | paint.setStrokeWidth(BASE_DOODLE_WIDTH);
89 | // rewind
90 | canvas.drawPath(path, paint);
91 | }
92 | }
93 |
94 | public void onDrawMosaic(Canvas canvas, Paint paint) {
95 | if (mode == IMGMode.MOSAIC) {
96 | paint.setStrokeWidth(width);
97 | canvas.drawPath(path, paint);
98 | }
99 | }
100 |
101 | public void transform(Matrix matrix) {
102 | path.transform(matrix);
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
24 |
25 |
39 |
40 |
48 |
49 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_gallery_sample.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
21 |
22 |
29 |
30 |
35 |
36 |
41 |
42 |
51 |
52 |
53 |
54 |
55 |
56 |
60 |
61 |
66 |
67 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/view/IMGStickerTextView.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.view;
2 |
3 | import android.content.Context;
4 | import android.graphics.Color;
5 | import android.util.AttributeSet;
6 | import android.util.TypedValue;
7 | import android.view.View;
8 | import android.widget.TextView;
9 |
10 | import me.minetsh.imaging.IMGTextEditDialog;
11 | import me.minetsh.imaging.core.IMGText;
12 |
13 | /**
14 | * Created by felix on 2017/11/14 下午7:27.
15 | */
16 | public class IMGStickerTextView extends IMGStickerView implements IMGTextEditDialog.Callback {
17 |
18 | private static final String TAG = "IMGStickerTextView";
19 |
20 | private TextView mTextView;
21 |
22 | private IMGText mText;
23 |
24 | private IMGTextEditDialog mDialog;
25 |
26 | private static float mBaseTextSize = -1f;
27 |
28 | private static final int PADDING = 26;
29 |
30 | private static final float TEXT_SIZE_SP = 24f;
31 |
32 | public IMGStickerTextView(Context context) {
33 | this(context, null, 0);
34 | }
35 |
36 | public IMGStickerTextView(Context context, AttributeSet attrs) {
37 | this(context, attrs, 0);
38 | }
39 |
40 | public IMGStickerTextView(Context context, AttributeSet attrs, int defStyleAttr) {
41 | super(context, attrs, defStyleAttr);
42 | }
43 |
44 | @Override
45 | public void onInitialize(Context context) {
46 | if (mBaseTextSize <= 0) {
47 | mBaseTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
48 | TEXT_SIZE_SP, context.getResources().getDisplayMetrics());
49 | }
50 | super.onInitialize(context);
51 | }
52 |
53 | @Override
54 | public View onCreateContentView(Context context) {
55 | mTextView = new TextView(context);
56 | mTextView.setTextSize(mBaseTextSize);
57 | mTextView.setPadding(PADDING, PADDING, PADDING, PADDING);
58 | mTextView.setTextColor(Color.WHITE);
59 |
60 | return mTextView;
61 | }
62 |
63 | public void setText(IMGText text) {
64 | mText = text;
65 | if (mText != null && mTextView != null) {
66 | mTextView.setText(mText.getText());
67 | mTextView.setTextColor(mText.getColor());
68 | }
69 | }
70 |
71 | public IMGText getText() {
72 | return mText;
73 | }
74 |
75 | @Override
76 | public void onContentTap() {
77 | IMGTextEditDialog dialog = getDialog();
78 | dialog.setText(mText);
79 | dialog.show();
80 | }
81 |
82 | private IMGTextEditDialog getDialog() {
83 | if (mDialog == null) {
84 | mDialog = new IMGTextEditDialog(getContext(), this);
85 | }
86 | return mDialog;
87 | }
88 |
89 | @Override
90 | public void onText(IMGText text) {
91 | mText = text;
92 | if (mText != null && mTextView != null) {
93 | mTextView.setText(mText.getText());
94 | mTextView.setTextColor(mText.getColor());
95 | }
96 | }
97 | }
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/gallery/model/IMGImageInfo.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.gallery.model;
2 |
3 | import android.net.Uri;
4 | import android.os.Parcel;
5 | import android.os.Parcelable;
6 |
7 | /**
8 | * Created by felix on 2017/12/26 上午11:09.
9 | */
10 |
11 | public class IMGImageInfo implements Parcelable {
12 |
13 | private long size;
14 |
15 | private int width;
16 |
17 | private int height;
18 |
19 | private boolean isOriginal;
20 |
21 | private boolean isEdited;
22 |
23 | private Uri uri;
24 |
25 | protected IMGImageInfo(Parcel in) {
26 | size = in.readLong();
27 | width = in.readInt();
28 | height = in.readInt();
29 | isOriginal = in.readByte() != 0;
30 | isEdited = in.readByte() != 0;
31 | uri = in.readParcelable(Uri.class.getClassLoader());
32 | }
33 |
34 | public IMGImageInfo(IMGImageViewModel model) {
35 | this.size = model.getSize();
36 | this.width = model.getWidth();
37 | this.height = model.getHeight();
38 | this.isOriginal = model.isOriginal();
39 | this.isEdited = false;
40 | this.uri = model.getUri();
41 | }
42 |
43 | public static final Creator CREATOR = new Creator() {
44 | @Override
45 | public IMGImageInfo createFromParcel(Parcel in) {
46 | return new IMGImageInfo(in);
47 | }
48 |
49 | @Override
50 | public IMGImageInfo[] newArray(int size) {
51 | return new IMGImageInfo[size];
52 | }
53 | };
54 |
55 | public long getSize() {
56 | return size;
57 | }
58 |
59 | public void setSize(long size) {
60 | this.size = size;
61 | }
62 |
63 | public int getWidth() {
64 | return width;
65 | }
66 |
67 | public void setWidth(int width) {
68 | this.width = width;
69 | }
70 |
71 | public int getHeight() {
72 | return height;
73 | }
74 |
75 | public void setHeight(int height) {
76 | this.height = height;
77 | }
78 |
79 | public boolean isOriginal() {
80 | return isOriginal;
81 | }
82 |
83 | public void setOriginal(boolean original) {
84 | isOriginal = original;
85 | }
86 |
87 | public boolean isEdited() {
88 | return isEdited;
89 | }
90 |
91 | public void setEdited(boolean edited) {
92 | isEdited = edited;
93 | }
94 |
95 | public Uri getUri() {
96 | return uri;
97 | }
98 |
99 | public void setUri(Uri uri) {
100 | this.uri = uri;
101 | }
102 |
103 | @Override
104 | public int describeContents() {
105 | return 0;
106 | }
107 |
108 | @Override
109 | public void writeToParcel(Parcel dest, int flags) {
110 | dest.writeLong(size);
111 | dest.writeInt(width);
112 | dest.writeInt(height);
113 | dest.writeByte((byte) (isOriginal ? 1 : 0));
114 | dest.writeByte((byte) (isEdited ? 1 : 0));
115 | dest.writeParcelable(uri, flags);
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/core/sticker/IMGStickerHelper.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.core.sticker;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.Matrix;
5 | import android.graphics.RectF;
6 | import android.view.View;
7 |
8 | /**
9 | * Created by felix on 2017/11/16 下午5:52.
10 | */
11 |
12 | public class IMGStickerHelper implements
13 | IMGStickerPortrait, IMGStickerPortrait.Callback {
14 |
15 | private RectF mFrame;
16 |
17 | private StickerView mView;
18 |
19 | private Callback mCallback;
20 |
21 | private boolean isShowing = false;
22 |
23 | public IMGStickerHelper(StickerView view) {
24 | mView = view;
25 | }
26 |
27 | @Override
28 | public boolean show() {
29 | if (!isShowing()) {
30 | isShowing = true;
31 | onShowing(mView);
32 | return true;
33 | }
34 | return false;
35 | }
36 |
37 | @Override
38 | public boolean remove() {
39 | return onRemove(mView);
40 | }
41 |
42 | @Override
43 | public boolean dismiss() {
44 | if (isShowing()) {
45 | isShowing = false;
46 | onDismiss(mView);
47 | return true;
48 | }
49 | return false;
50 | }
51 |
52 | @Override
53 | public boolean isShowing() {
54 | return isShowing;
55 | }
56 |
57 | @Override
58 | public RectF getFrame() {
59 | if (mFrame == null) {
60 | mFrame = new RectF(0, 0, mView.getWidth(), mView.getHeight());
61 | float pivotX = mView.getX() + mView.getPivotX();
62 | float pivotY = mView.getY() + mView.getPivotY();
63 |
64 | Matrix matrix = new Matrix();
65 | matrix.setTranslate(mView.getX(), mView.getY());
66 | matrix.postScale(mView.getScaleX(), mView.getScaleY(), pivotX, pivotY);
67 | matrix.mapRect(mFrame);
68 | }
69 | return mFrame;
70 | }
71 |
72 | @Override
73 | public void onSticker(Canvas canvas) {
74 | // empty
75 | }
76 |
77 | @Override
78 | public void registerCallback(Callback callback) {
79 | mCallback = callback;
80 | }
81 |
82 | @Override
83 | public void unregisterCallback(Callback callback) {
84 | mCallback = null;
85 | }
86 |
87 | @Override
88 | public boolean onRemove(V stickerView) {
89 | return mCallback != null && mCallback.onRemove(stickerView);
90 | }
91 |
92 | @Override
93 | public void onDismiss(V stickerView) {
94 | mFrame = null;
95 | stickerView.invalidate();
96 | if (mCallback != null) {
97 | mCallback.onDismiss(stickerView);
98 | }
99 | }
100 |
101 | @Override
102 | public void onShowing(V stickerView) {
103 | stickerView.invalidate();
104 | if (mCallback != null) {
105 | mCallback.onShowing(stickerView);
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/image/src/main/res/layout/image_edit_clip_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
27 |
28 |
34 |
35 |
46 |
47 |
58 |
59 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/core/sticker/IMGStickerAdjustHelper.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.core.sticker;
2 |
3 | import android.graphics.Matrix;
4 | import android.util.Log;
5 | import android.view.MotionEvent;
6 | import android.view.View;
7 |
8 | import me.minetsh.imaging.view.IMGStickerView;
9 |
10 | /**
11 | * Created by felix on 2017/11/15 下午5:44.
12 | */
13 |
14 | public class IMGStickerAdjustHelper implements View.OnTouchListener {
15 |
16 | private static final String TAG = "IMGStickerAdjustHelper";
17 |
18 | private View mView;
19 |
20 | private IMGStickerView mContainer;
21 |
22 | private float mCenterX, mCenterY;
23 |
24 | private double mRadius, mDegrees;
25 |
26 | private Matrix M = new Matrix();
27 |
28 | public IMGStickerAdjustHelper(IMGStickerView container, View view) {
29 | mView = view;
30 | mContainer = container;
31 | mView.setOnTouchListener(this);
32 | }
33 |
34 | @Override
35 | public boolean onTouch(View v, MotionEvent event) {
36 | switch (event.getAction()) {
37 | case MotionEvent.ACTION_DOWN:
38 |
39 | float x = event.getX();
40 |
41 | float y = event.getY();
42 |
43 | mCenterX = mCenterY = 0;
44 |
45 | float pointX = mView.getX() + x - mContainer.getPivotX();
46 |
47 | float pointY = mView.getY() + y - mContainer.getPivotY();
48 |
49 | Log.d(TAG, String.format("X=%f,Y=%f", pointX, pointY));
50 |
51 | mRadius = toLength(0, 0, pointX, pointY);
52 |
53 | mDegrees = toDegrees(pointY, pointX);
54 |
55 | M.setTranslate(pointX - x, pointY - y);
56 |
57 | Log.d(TAG, String.format("degrees=%f", toDegrees(pointY, pointX)));
58 |
59 | M.postRotate((float) -toDegrees(pointY, pointX), mCenterX, mCenterY);
60 |
61 | return true;
62 |
63 | case MotionEvent.ACTION_MOVE:
64 |
65 | float[] xy = {event.getX(), event.getY()};
66 |
67 | pointX = mView.getX() + xy[0] - mContainer.getPivotX();
68 |
69 | pointY = mView.getY() + xy[1] - mContainer.getPivotY();
70 |
71 | Log.d(TAG, String.format("X=%f,Y=%f", pointX, pointY));
72 |
73 | double radius = toLength(0, 0, pointX, pointY);
74 |
75 | double degrees = toDegrees(pointY, pointX);
76 |
77 | float scale = (float) (radius / mRadius);
78 |
79 |
80 | mContainer.addScale(scale);
81 |
82 | Log.d(TAG, " D = " + (degrees - mDegrees));
83 |
84 | mContainer.setRotation((float) (mContainer.getRotation() + degrees - mDegrees));
85 |
86 | mRadius = radius;
87 |
88 | return true;
89 | }
90 | return false;
91 | }
92 |
93 | private static double toDegrees(float v, float v1) {
94 | return Math.toDegrees(Math.atan2(v, v1));
95 | }
96 |
97 | private static double toLength(float x1, float y1, float x2, float y2) {
98 | return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/gallery/model/IMGChooseMode.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.gallery.model;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 |
6 | /**
7 | * Created by felix on 2018/1/4 下午2:28.
8 | */
9 |
10 | public class IMGChooseMode implements Parcelable {
11 |
12 | private boolean isOriginal = false;
13 |
14 | private boolean isOriginalChangeable = true;
15 |
16 | private boolean isSingleChoose = false;
17 |
18 | private int maxChooseCount = 9;
19 |
20 | public IMGChooseMode() {
21 |
22 | }
23 |
24 | protected IMGChooseMode(Parcel in) {
25 | isOriginal = in.readByte() != 0;
26 | isOriginalChangeable = in.readByte() != 0;
27 | isSingleChoose = in.readByte() != 0;
28 | maxChooseCount = in.readInt();
29 | }
30 |
31 | public static final Creator CREATOR = new Creator() {
32 | @Override
33 | public IMGChooseMode createFromParcel(Parcel in) {
34 | return new IMGChooseMode(in);
35 | }
36 |
37 | @Override
38 | public IMGChooseMode[] newArray(int size) {
39 | return new IMGChooseMode[size];
40 | }
41 | };
42 |
43 | @Override
44 | public int describeContents() {
45 | return 0;
46 | }
47 |
48 | @Override
49 | public void writeToParcel(Parcel dest, int flags) {
50 | dest.writeByte((byte) (isOriginal ? 1 : 0));
51 | dest.writeByte((byte) (isOriginalChangeable ? 1 : 0));
52 | dest.writeByte((byte) (isSingleChoose ? 1 : 0));
53 | dest.writeInt(maxChooseCount);
54 | }
55 |
56 | public boolean isOriginal() {
57 | return isOriginal;
58 | }
59 |
60 | public boolean isOriginalChangeable() {
61 | return isOriginalChangeable;
62 | }
63 |
64 | public boolean isSingleChoose() {
65 | return isSingleChoose;
66 | }
67 |
68 | public int getMaxChooseCount() {
69 | return maxChooseCount;
70 | }
71 |
72 | public static class Builder {
73 |
74 | IMGChooseMode mode;
75 |
76 | public Builder() {
77 | mode = new IMGChooseMode();
78 | }
79 |
80 | public Builder setOriginal(boolean original) {
81 | mode.isOriginal = original;
82 | return this;
83 | }
84 |
85 | public Builder setOriginalChangeable(boolean originalChangeable) {
86 | mode.isOriginalChangeable = originalChangeable;
87 | return this;
88 | }
89 |
90 | public Builder setSingleChoose(boolean single) {
91 | mode.isSingleChoose = single;
92 | if (single) {
93 | mode.maxChooseCount = 1;
94 | }
95 | return this;
96 | }
97 |
98 | public Builder setMaxChooseCount(int maxChooseCount) {
99 | mode.maxChooseCount = maxChooseCount;
100 | if (mode.isSingleChoose) {
101 | mode.maxChooseCount = Math.min(1, maxChooseCount);
102 | }
103 | return this;
104 | }
105 |
106 | public IMGChooseMode build() {
107 | return mode;
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/IMGTextEditDialog.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging;
2 |
3 | import android.app.Dialog;
4 | import android.content.Context;
5 | import android.graphics.Color;
6 | import android.os.Bundle;
7 | import android.text.TextUtils;
8 | import android.view.View;
9 | import android.view.Window;
10 | import android.view.WindowManager.LayoutParams;
11 | import android.widget.EditText;
12 | import android.widget.RadioGroup;
13 |
14 | import me.minetsh.imaging.core.IMGText;
15 | import me.minetsh.imaging.view.IMGColorGroup;
16 |
17 | /**
18 | * Created by felix on 2017/12/1 上午11:21.
19 | */
20 |
21 | public class IMGTextEditDialog extends Dialog implements View.OnClickListener,
22 | RadioGroup.OnCheckedChangeListener {
23 |
24 | private static final String TAG = "IMGTextEditDialog";
25 |
26 | private EditText mEditText;
27 |
28 | private Callback mCallback;
29 |
30 | private IMGText mDefaultText;
31 |
32 | private IMGColorGroup mColorGroup;
33 |
34 | public IMGTextEditDialog(Context context, Callback callback) {
35 | super(context, R.style.ImageTextDialog);
36 | setContentView(R.layout.image_text_dialog);
37 | mCallback = callback;
38 | Window window = getWindow();
39 | if (window != null) {
40 | window.setLayout(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
41 | }
42 | }
43 |
44 | @Override
45 | protected void onCreate(Bundle savedInstanceState) {
46 | super.onCreate(savedInstanceState);
47 |
48 | mColorGroup = findViewById(R.id.cg_colors);
49 | mColorGroup.setOnCheckedChangeListener(this);
50 | mEditText = findViewById(R.id.et_text);
51 |
52 | findViewById(R.id.tv_cancel).setOnClickListener(this);
53 | findViewById(R.id.tv_done).setOnClickListener(this);
54 | }
55 |
56 | @Override
57 | protected void onStart() {
58 | super.onStart();
59 | if (mDefaultText != null) {
60 | mEditText.setText(mDefaultText.getText());
61 | mEditText.setTextColor(mDefaultText.getColor());
62 | if (!mDefaultText.isEmpty()) {
63 | mEditText.setSelection(mEditText.length());
64 | }
65 | mDefaultText = null;
66 | } else mEditText.setText("");
67 | mColorGroup.setCheckColor(mEditText.getCurrentTextColor());
68 | }
69 |
70 | public void setText(IMGText text) {
71 | mDefaultText = text;
72 | }
73 |
74 | public void reset() {
75 | setText(new IMGText(null, Color.WHITE));
76 | }
77 |
78 | @Override
79 | public void onClick(View v) {
80 | int vid = v.getId();
81 | if (vid == R.id.tv_done) {
82 | onDone();
83 | } else if (vid == R.id.tv_cancel) {
84 | dismiss();
85 | }
86 | }
87 |
88 | private void onDone() {
89 | String text = mEditText.getText().toString();
90 | if (!TextUtils.isEmpty(text) && mCallback != null) {
91 | mCallback.onText(new IMGText(text, mEditText.getCurrentTextColor()));
92 | }
93 | dismiss();
94 | }
95 |
96 | @Override
97 | public void onCheckedChanged(RadioGroup group, int checkedId) {
98 | mEditText.setTextColor(mColorGroup.getCheckColor());
99 | }
100 |
101 | public interface Callback {
102 |
103 | void onText(IMGText text);
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/core/clip/IMGClip.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.core.clip;
2 |
3 | import android.graphics.RectF;
4 |
5 | /**
6 | * Created by felix on 2017/11/28 下午6:15.
7 | */
8 |
9 | public interface IMGClip {
10 |
11 | /**
12 | * 裁剪区域的边距
13 | */
14 | float CLIP_MARGIN = 60f;
15 |
16 | /**
17 | * 角尺寸
18 | */
19 | float CLIP_CORNER_SIZE = 48f;
20 |
21 | /**
22 | * 裁剪区域最小尺寸
23 | */
24 | float CLIP_FRAME_MIN = CLIP_CORNER_SIZE * 3.14f;
25 |
26 | /**
27 | * 内边厚度
28 | */
29 | float CLIP_THICKNESS_CELL = 3f;
30 |
31 | /**
32 | * 外边框厚度
33 | */
34 | float CLIP_THICKNESS_FRAME = 8f;
35 |
36 | /**
37 | * 角边厚度
38 | */
39 | float CLIP_THICKNESS_SEWING = 14f;
40 |
41 | /**
42 | * 比例尺,用于计算出 {0, width, 1/3 width, 2/3 width} & {0, height, 1/3 height, 2/3 height}
43 | */
44 | float[] CLIP_SIZE_RATIO = {0, 1, 0.33f, 0.66f};
45 |
46 | int CLIP_CELL_STRIDES = 0x7362DC98;
47 |
48 | int CLIP_CORNER_STRIDES = 0x0AAFF550;
49 |
50 | float[] CLIP_CORNER_STEPS = {0, 3, -3};
51 |
52 | float[] CLIP_CORNER_SIZES = {0, CLIP_CORNER_SIZE, -CLIP_CORNER_SIZE};
53 |
54 | byte[] CLIP_CORNERS = {
55 | 0x8, 0x8, 0x9, 0x8,
56 | 0x6, 0x8, 0x4, 0x8,
57 | 0x4, 0x8, 0x4, 0x1,
58 | 0x4, 0xA, 0x4, 0x8,
59 | 0x4, 0x4, 0x6, 0x4,
60 | 0x9, 0x4, 0x8, 0x4,
61 | 0x8, 0x4, 0x8, 0x6,
62 | 0x8, 0x9, 0x8, 0x8
63 | };
64 |
65 | enum Anchor {
66 | LEFT(1),
67 | RIGHT(2),
68 | TOP(4),
69 | BOTTOM(8),
70 | LEFT_TOP(5),
71 | RIGHT_TOP(6),
72 | LEFT_BOTTOM(9),
73 | RIGHT_BOTTOM(10);
74 |
75 | int v;
76 |
77 | /**
78 | * LEFT: 0
79 | * TOP: 2
80 | * RIGHT: 1
81 | * BOTTOM: 3
82 | */
83 |
84 | final static int P = 0, N = 1;
85 |
86 | final static int H = 0, V = 2;
87 |
88 | final static int[] PN = {1, -1};
89 |
90 | Anchor(int v) {
91 | this.v = v;
92 | }
93 |
94 | public void move(RectF win, RectF frame, float dx, float dy) {
95 | float[] maxFrame = cohesion(win, CLIP_MARGIN);
96 | float[] minFrame = cohesion(frame, CLIP_FRAME_MIN);
97 | float[] theFrame = cohesion(frame, 0);
98 |
99 | float[] dxy = {dx, 0, dy};
100 | for (int i = 0; i < 4; i++) {
101 | if (((1 << i) & v) != 0) {
102 |
103 | int pn = PN[i & 1];
104 |
105 | theFrame[i] = pn * revise(pn * (theFrame[i] + dxy[i & 2]),
106 | pn * maxFrame[i], pn * minFrame[i + PN[i & 1]]);
107 | }
108 | }
109 |
110 | frame.set(theFrame[0], theFrame[2], theFrame[1], theFrame[3]);
111 | }
112 |
113 | public static float revise(float v, float min, float max) {
114 | return Math.min(Math.max(v, min), max);
115 | }
116 |
117 | public static float[] cohesion(RectF win, float v) {
118 | return new float[]{
119 | win.left + v, win.right - v,
120 | win.top + v, win.bottom - v
121 | };
122 | }
123 |
124 | public static boolean isCohesionContains(RectF frame, float v, float x, float y) {
125 | return frame.left + v < x && frame.right - v > x
126 | && frame.top + v < y && frame.bottom - v > y;
127 | }
128 |
129 | public static Anchor valueOf(int v) {
130 | Anchor[] values = values();
131 | for (Anchor anchor : values) {
132 | if (anchor.v == v) {
133 | return anchor;
134 | }
135 | }
136 | return null;
137 | }
138 | }
139 | }
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/view/IMGColorRadio.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.view;
2 |
3 | import android.animation.ValueAnimator;
4 | import android.content.Context;
5 | import android.content.res.TypedArray;
6 | import android.graphics.Canvas;
7 | import android.graphics.Color;
8 | import android.graphics.Paint;
9 | import android.util.AttributeSet;
10 | import android.view.animation.AccelerateDecelerateInterpolator;
11 | import android.widget.RadioButton;
12 |
13 | import me.minetsh.imaging.R;
14 |
15 | /**
16 | * Created by felix on 2017/12/1 下午2:50.
17 | */
18 |
19 | public class IMGColorRadio extends RadioButton implements ValueAnimator.AnimatorUpdateListener {
20 |
21 | private static final String TAG = "IMGColorRadio";
22 |
23 | private int mColor = Color.WHITE;
24 |
25 | private int mStrokeColor = Color.WHITE;
26 |
27 | private float mRadiusRatio = 0f;
28 |
29 | private ValueAnimator mAnimator;
30 |
31 | private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
32 |
33 | private static final float RADIUS_BASE = 0.6f;
34 |
35 | private static final float RADIUS_RING = 0.9f;
36 |
37 | private static final float RADIUS_BALL = 0.72f;
38 |
39 | public IMGColorRadio(Context context) {
40 | this(context, null, 0);
41 | }
42 |
43 | public IMGColorRadio(Context context, AttributeSet attrs) {
44 | super(context, attrs);
45 | initialize(context, attrs, 0);
46 | }
47 |
48 | public IMGColorRadio(Context context, AttributeSet attrs, int defStyleAttr) {
49 | super(context, attrs, defStyleAttr);
50 | initialize(context, attrs, defStyleAttr);
51 | }
52 |
53 | private void initialize(Context context, AttributeSet attrs, int defStyleAttr) {
54 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.IMGColorRadio);
55 |
56 | mColor = a.getColor(R.styleable.IMGColorRadio_image_color, Color.WHITE);
57 | mStrokeColor = a.getColor(R.styleable.IMGColorRadio_image_stroke_color, Color.WHITE);
58 |
59 | a.recycle();
60 |
61 | setButtonDrawable(null);
62 |
63 | mPaint.setColor(mColor);
64 | mPaint.setStrokeWidth(5f);
65 | }
66 |
67 | private ValueAnimator getAnimator() {
68 | if (mAnimator == null) {
69 | mAnimator = ValueAnimator.ofFloat(0f, 1f);
70 | mAnimator.addUpdateListener(this);
71 | mAnimator.setDuration(200);
72 | mAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
73 | }
74 | return mAnimator;
75 | }
76 |
77 | public void setColor(int color) {
78 | mColor = color;
79 | mPaint.setColor(mColor);
80 | }
81 |
82 | public int getColor() {
83 | return mColor;
84 | }
85 |
86 | @Override
87 | public void draw(Canvas canvas) {
88 | super.draw(canvas);
89 |
90 | float hw = getWidth() / 2f, hh = getHeight() / 2f;
91 | float radius = Math.min(hw, hh);
92 |
93 | canvas.save();
94 | mPaint.setColor(mColor);
95 | mPaint.setStyle(Paint.Style.FILL);
96 | canvas.drawCircle(hw, hh, getBallRadius(radius), mPaint);
97 |
98 | mPaint.setColor(mStrokeColor);
99 | mPaint.setStyle(Paint.Style.STROKE);
100 | canvas.drawCircle(hw, hh, getRingRadius(radius), mPaint);
101 | canvas.restore();
102 | }
103 |
104 | private float getBallRadius(float radius) {
105 | return radius * ((RADIUS_BALL - RADIUS_BASE) * mRadiusRatio + RADIUS_BASE);
106 | }
107 |
108 | private float getRingRadius(float radius) {
109 | return radius * ((RADIUS_RING - RADIUS_BASE) * mRadiusRatio + RADIUS_BASE);
110 | }
111 |
112 | @Override
113 | public void setChecked(boolean checked) {
114 | boolean isChanged = checked != isChecked();
115 |
116 | super.setChecked(checked);
117 |
118 | if (isChanged) {
119 | ValueAnimator animator = getAnimator();
120 |
121 | if (checked) {
122 | animator.start();
123 | } else {
124 | animator.reverse();
125 | }
126 | }
127 | }
128 |
129 | @Override
130 | public void onAnimationUpdate(ValueAnimator animation) {
131 | mRadiusRatio = (float) animation.getAnimatedValue();
132 | invalidate();
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/gallery/IMGScanner.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.gallery;
2 |
3 | import android.content.ContentResolver;
4 | import android.content.Context;
5 | import android.database.Cursor;
6 | import android.graphics.BitmapFactory;
7 | import android.net.Uri;
8 | import android.provider.MediaStore;
9 | import android.text.TextUtils;
10 |
11 | import me.minetsh.imaging.gallery.model.IMGImageViewModel;
12 |
13 | import java.io.File;
14 | import java.util.ArrayList;
15 | import java.util.LinkedHashMap;
16 | import java.util.List;
17 | import java.util.Map;
18 |
19 | /**
20 | * Created by felix on 2018/1/4 下午1:27.
21 | */
22 |
23 | public class IMGScanner {
24 |
25 | private static final String[] SELECTION_ARGS = new String[]{
26 | "image/jpeg", "image/png", "image/webp"
27 | };
28 |
29 | private static final String SELECTION = String.format(
30 | "%s=? or %s=? or %s=?",
31 | MediaStore.Images.Media.MIME_TYPE,
32 | MediaStore.Images.Media.MIME_TYPE,
33 | MediaStore.Images.Media.MIME_TYPE
34 | );
35 |
36 | public static final String ALL_IMAGES = "所有图片";
37 |
38 | public static Map> getImages14(Context context, int count, Callback callback) {
39 | LinkedHashMap> images = new LinkedHashMap<>();
40 |
41 | ContentResolver contentResolver = context.getContentResolver();
42 |
43 | BitmapFactory.Options options = new BitmapFactory.Options();
44 | options.inJustDecodeBounds = true;
45 |
46 | Cursor cursor = null;
47 |
48 | try {
49 |
50 | cursor = contentResolver.query(
51 | MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
52 | new String[]{
53 | MediaStore.Images.Media.DATA,
54 | MediaStore.Images.Media.SIZE
55 | },
56 | SELECTION, SELECTION_ARGS,
57 | MediaStore.Images.Media.DATE_MODIFIED + " desc"
58 | );
59 |
60 | List allInfos = new ArrayList<>();
61 | images.put(ALL_IMAGES, allInfos);
62 | if (cursor == null) {
63 | return images;
64 | }
65 | while (cursor.moveToNext()) {
66 |
67 | long size = cursor.getLong(
68 | cursor.getColumnIndex(MediaStore.Images.Media.SIZE)
69 | );
70 |
71 | String path = cursor.getString(
72 | cursor.getColumnIndex(MediaStore.Images.Media.DATA)
73 | );
74 |
75 | if (size == 0 || TextUtils.isEmpty(path)) {
76 | continue;
77 | }
78 |
79 | File file = new File(path);
80 | if (file.exists()) {
81 | // 通过读取文件获取图片宽高
82 | BitmapFactory.decodeFile(path, options);
83 | IMGImageViewModel model = new IMGImageViewModel(Uri.fromFile(file));
84 | model.setWidth(options.outWidth);
85 | model.setHeight(options.outHeight);
86 | model.setSize(size);
87 | File parentFile = file.getParentFile();
88 | if (parentFile != null && parentFile.exists()) {
89 | String folderName = file.getParentFile().getName();
90 | List infos = images.get(folderName);
91 | if (infos == null) {
92 | infos = new ArrayList<>();
93 | images.put(folderName, infos);
94 | }
95 | infos.add(model);
96 | allInfos.add(model);
97 | if (callback != null && allInfos.size() >= count) {
98 | callback.onImages(allInfos);
99 | }
100 | }
101 | }
102 | }
103 | return images;
104 |
105 | } finally {
106 | if (cursor != null) {
107 | cursor.close();
108 | }
109 | }
110 | }
111 |
112 | public interface Callback {
113 |
114 | void onImages(List images);
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/sample/src/main/java/me/minetsh/imaging/sample/GallerySampleActivity.kt:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.sample
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.os.Bundle
7 | import android.view.LayoutInflater
8 | import android.view.View
9 | import android.view.ViewGroup
10 | import androidx.appcompat.app.AppCompatActivity
11 | import androidx.recyclerview.widget.RecyclerView
12 | import com.facebook.drawee.backends.pipeline.Fresco
13 | import com.facebook.drawee.view.SimpleDraweeView
14 | import com.facebook.imagepipeline.common.ResizeOptions
15 | import com.facebook.imagepipeline.common.RotationOptions
16 | import com.facebook.imagepipeline.request.ImageRequestBuilder
17 | import kotlinx.android.synthetic.main.activity_gallery_sample.*
18 | import me.minetsh.imaging.IMGGalleryActivity
19 | import me.minetsh.imaging.gallery.model.IMGChooseMode
20 | import me.minetsh.imaging.gallery.model.IMGImageInfo
21 |
22 | /**
23 | * Created by felix on 2018/1/4 下午4:36.
24 | */
25 |
26 | class GallerySampleActivity : AppCompatActivity() {
27 |
28 | private var mAdapter: GalleryImageAdapter? = null
29 |
30 | override fun onCreate(savedInstanceState: Bundle?) {
31 | super.onCreate(savedInstanceState)
32 | setContentView(R.layout.activity_gallery_sample)
33 |
34 | mAdapter = GalleryImageAdapter(this)
35 | rv_images.adapter = mAdapter
36 |
37 | btn_choose.setOnClickListener {
38 | chooseImages()
39 | }
40 |
41 | btn_custom.setOnClickListener {
42 | chooseImagesFromCustomAlbum()
43 | }
44 | }
45 |
46 | private fun chooseImages() {
47 | val builder = IMGChooseMode.Builder()
48 |
49 | builder.setSingleChoose(rg_modes.checkedRadioButtonId == R.id.rb_single)
50 | builder.setMaxChooseCount(et_max_count.text.toString().toInt())
51 |
52 | startActivityForResult(
53 | IMGGalleryActivity.newIntent(this, builder.build()),
54 | REQ_IMAGE_CHOOSE
55 | )
56 | }
57 |
58 | private fun chooseImagesFromCustomAlbum() {
59 | val builder = IMGChooseMode.Builder()
60 |
61 | builder.setSingleChoose(rg_modes.checkedRadioButtonId == R.id.rb_single)
62 | builder.setMaxChooseCount(et_max_count.text.toString().toInt())
63 |
64 | startActivityForResult(
65 | IMGGalleryActivity.newIntent(this, builder.build())
66 | .setClass(this, CustomGalleryActivity::class.java),
67 | REQ_IMAGE_CHOOSE
68 | )
69 | }
70 |
71 | private fun onChooseImages(images: List?) {
72 | mAdapter?.mImages = images
73 | mAdapter?.notifyDataSetChanged()
74 | }
75 |
76 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
77 | when (requestCode) {
78 | REQ_IMAGE_CHOOSE -> {
79 | if (resultCode == Activity.RESULT_OK) {
80 | onChooseImages(IMGGalleryActivity.getImageInfos(data))
81 | }
82 | }
83 | }
84 | super.onActivityResult(requestCode, resultCode, data)
85 | }
86 | }
87 |
88 | class GalleryImageAdapter(private val context: Context) : RecyclerView.Adapter() {
89 |
90 | var mImages: List? = null
91 |
92 | private val layoutInflater by lazy {
93 | LayoutInflater.from(context)
94 | }
95 |
96 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GalleryImageViewHolder {
97 | return GalleryImageViewHolder(layoutInflater.inflate(R.layout.layout_image, parent, false))
98 | }
99 |
100 | override fun onBindViewHolder(holder: GalleryImageViewHolder, position: Int) {
101 | val item = getItem(position)
102 | if (item != null) {
103 | holder.update(item)
104 | }
105 | }
106 |
107 | private fun getItem(position: Int): IMGImageInfo? {
108 | return if (position in 0..(itemCount - 1)) mImages?.get(position) else null
109 | }
110 |
111 | override fun getItemCount(): Int {
112 | return mImages?.size ?: 0
113 | }
114 | }
115 |
116 | class GalleryImageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
117 |
118 | private val imageView: SimpleDraweeView = itemView.findViewById(R.id.sdv_image)
119 |
120 | fun update(image: IMGImageInfo) {
121 | val request = ImageRequestBuilder.newBuilderWithSource(image.uri)
122 | .setLocalThumbnailPreviewsEnabled(true)
123 | .setResizeOptions(ResizeOptions(300, 300))
124 | .setRotationOptions(RotationOptions.autoRotate())
125 | .build()
126 |
127 | val controller = Fresco.newDraweeControllerBuilder()
128 | .setOldController(imageView.controller)
129 | .setImageRequest(request)
130 | .build()
131 |
132 | imageView.controller = controller
133 | }
134 | }
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/IMGEditActivity.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging;
2 |
3 | import android.content.Intent;
4 | import android.graphics.Bitmap;
5 | import android.graphics.BitmapFactory;
6 | import android.net.Uri;
7 | import android.text.TextUtils;
8 |
9 | import me.minetsh.imaging.core.IMGMode;
10 | import me.minetsh.imaging.core.IMGText;
11 | import me.minetsh.imaging.core.file.IMGAssetFileDecoder;
12 | import me.minetsh.imaging.core.file.IMGDecoder;
13 | import me.minetsh.imaging.core.file.IMGFileDecoder;
14 | import me.minetsh.imaging.core.util.IMGUtils;
15 |
16 | import java.io.FileNotFoundException;
17 | import java.io.FileOutputStream;
18 | import java.io.IOException;
19 |
20 | /**
21 | * Created by felix on 2017/11/14 下午2:26.
22 | */
23 |
24 | public class IMGEditActivity extends IMGEditBaseActivity {
25 |
26 | private static final int MAX_WIDTH = 1024;
27 |
28 | private static final int MAX_HEIGHT = 1024;
29 |
30 | public static final String EXTRA_IMAGE_URI = "IMAGE_URI";
31 |
32 | public static final String EXTRA_IMAGE_SAVE_PATH = "IMAGE_SAVE_PATH";
33 |
34 | @Override
35 | public void onCreated() {
36 |
37 | }
38 |
39 | @Override
40 | public Bitmap getBitmap() {
41 | Intent intent = getIntent();
42 | if (intent == null) {
43 | return null;
44 | }
45 |
46 | Uri uri = intent.getParcelableExtra(EXTRA_IMAGE_URI);
47 | if (uri == null) {
48 | return null;
49 | }
50 |
51 | IMGDecoder decoder = null;
52 |
53 | String path = uri.getPath();
54 | if (!TextUtils.isEmpty(path)) {
55 | switch (uri.getScheme()) {
56 | case "asset":
57 | decoder = new IMGAssetFileDecoder(this, uri);
58 | break;
59 | case "file":
60 | decoder = new IMGFileDecoder(uri);
61 | break;
62 | }
63 | }
64 |
65 | if (decoder == null) {
66 | return null;
67 | }
68 |
69 | BitmapFactory.Options options = new BitmapFactory.Options();
70 | options.inSampleSize = 1;
71 | options.inJustDecodeBounds = true;
72 |
73 | decoder.decode(options);
74 |
75 | if (options.outWidth > MAX_WIDTH) {
76 | options.inSampleSize = IMGUtils.inSampleSize(Math.round(1f * options.outWidth / MAX_WIDTH));
77 | }
78 |
79 | if (options.outHeight > MAX_HEIGHT) {
80 | options.inSampleSize = Math.max(options.inSampleSize,
81 | IMGUtils.inSampleSize(Math.round(1f * options.outHeight / MAX_HEIGHT)));
82 | }
83 |
84 | options.inJustDecodeBounds = false;
85 |
86 | Bitmap bitmap = decoder.decode(options);
87 | if (bitmap == null) {
88 | return null;
89 | }
90 |
91 | return bitmap;
92 | }
93 |
94 | @Override
95 | public void onText(IMGText text) {
96 | mImgView.addStickerText(text);
97 | }
98 |
99 | @Override
100 | public void onModeClick(IMGMode mode) {
101 | IMGMode cm = mImgView.getMode();
102 | if (cm == mode) {
103 | mode = IMGMode.NONE;
104 | }
105 | mImgView.setMode(mode);
106 | updateModeUI();
107 |
108 | if (mode == IMGMode.CLIP) {
109 | setOpDisplay(OP_CLIP);
110 | }
111 | }
112 |
113 | @Override
114 | public void onUndoClick() {
115 | IMGMode mode = mImgView.getMode();
116 | if (mode == IMGMode.DOODLE) {
117 | mImgView.undoDoodle();
118 | } else if (mode == IMGMode.MOSAIC) {
119 | mImgView.undoMosaic();
120 | }
121 | }
122 |
123 | @Override
124 | public void onCancelClick() {
125 | finish();
126 | }
127 |
128 | @Override
129 | public void onDoneClick() {
130 | String path = getIntent().getStringExtra(EXTRA_IMAGE_SAVE_PATH);
131 | if (!TextUtils.isEmpty(path)) {
132 | Bitmap bitmap = mImgView.saveBitmap();
133 | if (bitmap != null) {
134 | FileOutputStream fout = null;
135 | try {
136 | fout = new FileOutputStream(path);
137 | bitmap.compress(Bitmap.CompressFormat.JPEG, 80, fout);
138 | } catch (FileNotFoundException e) {
139 | e.printStackTrace();
140 | } finally {
141 | if (fout != null) {
142 | try {
143 | fout.close();
144 | } catch (IOException e) {
145 | e.printStackTrace();
146 | }
147 | }
148 | }
149 | setResult(RESULT_OK);
150 | finish();
151 | return;
152 | }
153 | }
154 | setResult(RESULT_CANCELED);
155 | finish();
156 | }
157 |
158 | @Override
159 | public void onCancelClipClick() {
160 | mImgView.cancelClip();
161 | setOpDisplay(mImgView.getMode() == IMGMode.CLIP ? OP_CLIP : OP_NORMAL);
162 | }
163 |
164 | @Override
165 | public void onDoneClipClick() {
166 | mImgView.doClip();
167 | setOpDisplay(mImgView.getMode() == IMGMode.CLIP ? OP_CLIP : OP_NORMAL);
168 | }
169 |
170 | @Override
171 | public void onResetClipClick() {
172 | mImgView.resetClip();
173 | }
174 |
175 | @Override
176 | public void onRotateClipClick() {
177 | mImgView.doRotate();
178 | }
179 |
180 | @Override
181 | public void onColorChanged(int checkedColor) {
182 | mImgView.setPenColor(checkedColor);
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/IMGEditBaseActivity.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging;
2 |
3 | import android.app.Activity;
4 | import android.content.DialogInterface;
5 | import android.graphics.Bitmap;
6 | import android.os.Bundle;
7 | import android.view.View;
8 | import android.widget.RadioGroup;
9 | import android.widget.ViewSwitcher;
10 |
11 | import me.minetsh.imaging.core.IMGMode;
12 | import me.minetsh.imaging.core.IMGText;
13 | import me.minetsh.imaging.view.IMGColorGroup;
14 | import me.minetsh.imaging.view.IMGView;
15 |
16 | /**
17 | * Created by felix on 2017/12/5 下午3:08.
18 | */
19 |
20 | abstract class IMGEditBaseActivity extends Activity implements View.OnClickListener,
21 | IMGTextEditDialog.Callback, RadioGroup.OnCheckedChangeListener,
22 | DialogInterface.OnShowListener, DialogInterface.OnDismissListener {
23 |
24 | protected IMGView mImgView;
25 |
26 | private RadioGroup mModeGroup;
27 |
28 | private IMGColorGroup mColorGroup;
29 |
30 | private IMGTextEditDialog mTextDialog;
31 |
32 | private View mLayoutOpSub;
33 |
34 | private ViewSwitcher mOpSwitcher, mOpSubSwitcher;
35 |
36 | public static final int OP_HIDE = -1;
37 |
38 | public static final int OP_NORMAL = 0;
39 |
40 | public static final int OP_CLIP = 1;
41 |
42 | public static final int OP_SUB_DOODLE = 0;
43 |
44 | public static final int OP_SUB_MOSAIC = 1;
45 |
46 | @Override
47 | protected void onCreate(Bundle savedInstanceState) {
48 | super.onCreate(savedInstanceState);
49 | Bitmap bitmap = getBitmap();
50 | if (bitmap != null) {
51 | setContentView(R.layout.image_edit_activity);
52 | initViews();
53 | mImgView.setImageBitmap(bitmap);
54 | onCreated();
55 | } else finish();
56 | }
57 |
58 | public void onCreated() {
59 |
60 | }
61 |
62 | private void initViews() {
63 | mImgView = findViewById(R.id.image_canvas);
64 | mModeGroup = findViewById(R.id.rg_modes);
65 |
66 | mOpSwitcher = findViewById(R.id.vs_op);
67 | mOpSubSwitcher = findViewById(R.id.vs_op_sub);
68 |
69 | mColorGroup = findViewById(R.id.cg_colors);
70 | mColorGroup.setOnCheckedChangeListener(this);
71 |
72 | mLayoutOpSub = findViewById(R.id.layout_op_sub);
73 | }
74 |
75 | @Override
76 | public void onClick(View v) {
77 | int vid = v.getId();
78 | if (vid == R.id.rb_doodle) {
79 | onModeClick(IMGMode.DOODLE);
80 | } else if (vid == R.id.btn_text) {
81 | onTextModeClick();
82 | } else if (vid == R.id.rb_mosaic) {
83 | onModeClick(IMGMode.MOSAIC);
84 | } else if (vid == R.id.btn_clip) {
85 | onModeClick(IMGMode.CLIP);
86 | } else if (vid == R.id.btn_undo) {
87 | onUndoClick();
88 | } else if (vid == R.id.tv_done) {
89 | onDoneClick();
90 | } else if (vid == R.id.tv_cancel) {
91 | onCancelClick();
92 | } else if (vid == R.id.ib_clip_cancel) {
93 | onCancelClipClick();
94 | } else if (vid == R.id.ib_clip_done) {
95 | onDoneClipClick();
96 | } else if (vid == R.id.tv_clip_reset) {
97 | onResetClipClick();
98 | } else if (vid == R.id.ib_clip_rotate) {
99 | onRotateClipClick();
100 | }
101 | }
102 |
103 | public void updateModeUI() {
104 | IMGMode mode = mImgView.getMode();
105 | switch (mode) {
106 | case DOODLE:
107 | mModeGroup.check(R.id.rb_doodle);
108 | setOpSubDisplay(OP_SUB_DOODLE);
109 | break;
110 | case MOSAIC:
111 | mModeGroup.check(R.id.rb_mosaic);
112 | setOpSubDisplay(OP_SUB_MOSAIC);
113 | break;
114 | case NONE:
115 | mModeGroup.clearCheck();
116 | setOpSubDisplay(OP_HIDE);
117 | break;
118 | }
119 | }
120 |
121 | public void onTextModeClick() {
122 | if (mTextDialog == null) {
123 | mTextDialog = new IMGTextEditDialog(this, this);
124 | mTextDialog.setOnShowListener(this);
125 | mTextDialog.setOnDismissListener(this);
126 | }
127 | mTextDialog.show();
128 | }
129 |
130 | @Override
131 | public final void onCheckedChanged(RadioGroup group, int checkedId) {
132 | onColorChanged(mColorGroup.getCheckColor());
133 | }
134 |
135 | public void setOpDisplay(int op) {
136 | if (op >= 0) {
137 | mOpSwitcher.setDisplayedChild(op);
138 | }
139 | }
140 |
141 | public void setOpSubDisplay(int opSub) {
142 | if (opSub < 0) {
143 | mLayoutOpSub.setVisibility(View.GONE);
144 | } else {
145 | mOpSubSwitcher.setDisplayedChild(opSub);
146 | mLayoutOpSub.setVisibility(View.VISIBLE);
147 | }
148 | }
149 |
150 | @Override
151 | public void onShow(DialogInterface dialog) {
152 | mOpSwitcher.setVisibility(View.GONE);
153 | }
154 |
155 | @Override
156 | public void onDismiss(DialogInterface dialog) {
157 | mOpSwitcher.setVisibility(View.VISIBLE);
158 | }
159 |
160 | public abstract Bitmap getBitmap();
161 |
162 | public abstract void onModeClick(IMGMode mode);
163 |
164 | public abstract void onUndoClick();
165 |
166 | public abstract void onCancelClick();
167 |
168 | public abstract void onDoneClick();
169 |
170 | public abstract void onCancelClipClick();
171 |
172 | public abstract void onDoneClipClick();
173 |
174 | public abstract void onResetClipClick();
175 |
176 | public abstract void onRotateClipClick();
177 |
178 | public abstract void onColorChanged(int checkedColor);
179 |
180 | @Override
181 | public abstract void onText(IMGText text);
182 | }
183 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/core/sticker/IMGStickerX.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.core.sticker;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.Color;
5 | import android.graphics.Matrix;
6 | import android.graphics.Paint;
7 | import android.graphics.RectF;
8 | import android.view.MotionEvent;
9 |
10 | /**
11 | * Created by felix on 2017/12/11 下午2:48.
12 | */
13 |
14 | public class IMGStickerX {
15 |
16 | private float mBaseScale = 1f, mScale = 1f;
17 |
18 | private float mBaseRotate = 0f, mRotate = 0f;
19 |
20 | private float mX = 0f, mY = 0f;
21 |
22 | protected float[] mPivotXY = {0, 0};
23 |
24 | private StickerEvent mTouchEvent;
25 |
26 | private boolean isActivated = true;
27 |
28 | /**
29 | * isActivated 为true时,其坐标相对于屏幕左上角
30 | * isActivated 为false时,其坐标相对Image,切为单位坐标
31 | */
32 | protected RectF mFrame = new RectF();
33 |
34 | private RectF mRemoveFrame = new RectF();
35 |
36 | private RectF mAdjustFrame = new RectF();
37 |
38 | private final static float SIZE_ANCHOR = 60;
39 |
40 | private final static float STROKE_FRAME = 6f;
41 |
42 | private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
43 |
44 | {
45 | mPaint.setColor(Color.RED);
46 | mPaint.setStrokeWidth(STROKE_FRAME);
47 | mPaint.setStyle(Paint.Style.STROKE);
48 |
49 | mFrame.set(0, 0, SIZE_ANCHOR * 2, SIZE_ANCHOR * 2);
50 | mRemoveFrame.set(0, 0, SIZE_ANCHOR, SIZE_ANCHOR);
51 | mAdjustFrame.set(0, 0, SIZE_ANCHOR, SIZE_ANCHOR);
52 | }
53 |
54 | public boolean isActivated() {
55 | return isActivated;
56 | }
57 |
58 | public void setActivated(boolean activated) {
59 | isActivated = activated;
60 |
61 | }
62 |
63 | public void onMeasure(float width, float height) {
64 | mFrame.set(0, 0, width, height);
65 | mFrame.offset(mPivotXY[0] - mFrame.centerX(), mPivotXY[1] - mFrame.centerY());
66 | }
67 |
68 | public void onDraw(Canvas canvas) {
69 | if (isActivated) {
70 | canvas.save();
71 |
72 | canvas.rotate(mRotate, mPivotXY[0], mPivotXY[1]);
73 |
74 | canvas.drawRect(mFrame, mPaint);
75 |
76 | canvas.translate(mFrame.left, mFrame.top);
77 |
78 | canvas.drawRect(mRemoveFrame, mPaint);
79 |
80 | canvas.translate(mFrame.width() - mAdjustFrame.width(), mFrame.height() - mAdjustFrame.height());
81 |
82 | canvas.drawRect(mAdjustFrame, mPaint);
83 |
84 | canvas.restore();
85 | }
86 |
87 | canvas.rotate(mRotate, mPivotXY[0], mPivotXY[1]);
88 |
89 | // canvas.scale(mBaseScale * mScale, mBaseScale * mScale, mPivotXY[0], mPivotXY[1]);
90 | }
91 |
92 | public void setScale(float scale) {
93 | mScale = scale;
94 | }
95 |
96 | public void setRotate(float rotate) {
97 | mRotate = rotate;
98 | }
99 |
100 | public void setBaseScale(float baseScale) {
101 | mBaseScale = baseScale;
102 | }
103 |
104 | public void setBaseRotate(float baseRotate) {
105 | mBaseRotate = baseRotate;
106 | }
107 |
108 | public void offset(float dx, float dy) {
109 | mPivotXY[0] += dx;
110 | mPivotXY[1] += dy;
111 | mFrame.offset(mPivotXY[0] - mFrame.centerX(), mPivotXY[1] - mFrame.centerY());
112 | }
113 |
114 | public StickerEvent onTouch(MotionEvent event) {
115 | int action = event.getActionMasked();
116 |
117 | if (mTouchEvent == null && action != MotionEvent.ACTION_DOWN) {
118 | return null;
119 | }
120 |
121 | switch (action) {
122 | case MotionEvent.ACTION_DOWN:
123 | mX = event.getX();
124 | mY = event.getY();
125 | mTouchEvent = getTouchEvent(mX, mY);
126 |
127 | return mTouchEvent;
128 | case MotionEvent.ACTION_MOVE:
129 | if (mTouchEvent == StickerEvent.BODY) {
130 | offset(event.getX() - mX, event.getY() - mY);
131 | mX = event.getX();
132 | mY = event.getY();
133 | }
134 | default:
135 | return mTouchEvent;
136 | }
137 | }
138 |
139 | private StickerEvent getTouchEvent(float x, float y) {
140 | float[] xy = {x, y};
141 | Matrix matrix = new Matrix();
142 | matrix.setRotate(mRotate, mFrame.centerX(), mFrame.centerY());
143 | matrix.mapPoints(xy);
144 |
145 | if (mFrame.contains(xy[0], xy[1])) {
146 | if (isInsideRemove(xy[0], xy[1])) {
147 | // 触摸到删除按钮
148 | return mTouchEvent = StickerEvent.REMOVE;
149 | } else if (isInsideAdjust(xy[0], xy[1])) {
150 | // 触摸到调整按钮
151 | return mTouchEvent = StickerEvent.ADJUST;
152 | }
153 | return StickerEvent.BODY;
154 | }
155 | return null;
156 | }
157 |
158 | public void setTouchEvent(StickerEvent touchEvent) {
159 | mTouchEvent = touchEvent;
160 | }
161 |
162 | public boolean isInsideRemove(float x, float y) {
163 | return mRemoveFrame.contains(x - mFrame.left, y - mFrame.top);
164 | }
165 |
166 | public boolean isInsideAdjust(float x, float y) {
167 | return mAdjustFrame.contains(
168 | x - mFrame.right + mAdjustFrame.width(),
169 | y - mFrame.bottom + mAdjustFrame.height()
170 | );
171 | }
172 |
173 | public boolean isInside(float x, float y) {
174 | float[] xy = {x, y};
175 | Matrix matrix = new Matrix();
176 | matrix.setRotate(mRotate, mFrame.centerX(), mFrame.centerY());
177 | matrix.mapPoints(xy);
178 |
179 | return mFrame.contains(xy[0], xy[1]);
180 | }
181 |
182 | public void transform(Matrix matrix) {
183 | matrix.mapPoints(mPivotXY);
184 | mFrame.offset(mPivotXY[0] - mFrame.centerX(), mPivotXY[1] - mFrame.centerY());
185 | }
186 |
187 | public enum StickerEvent {
188 | REMOVE,
189 | BODY,
190 | ADJUST
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/sample/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 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/gallery/IMGGalleryMenuWindow.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.gallery;
2 |
3 | import android.content.Context;
4 | import android.graphics.drawable.ColorDrawable;
5 | import android.net.Uri;
6 | import android.util.DisplayMetrics;
7 | import android.view.LayoutInflater;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.view.WindowManager;
11 | import android.widget.PopupWindow;
12 | import android.widget.RadioButton;
13 | import android.widget.TextView;
14 |
15 | import androidx.recyclerview.widget.RecyclerView;
16 |
17 | import com.facebook.drawee.view.SimpleDraweeView;
18 |
19 | import java.util.ArrayList;
20 | import java.util.List;
21 |
22 | import me.minetsh.imaging.R;
23 | import me.minetsh.imaging.widget.IMGViewHolderCallback;
24 |
25 | /**
26 | * Created by felix on 2018/1/5 下午1:51.
27 | */
28 |
29 | public class IMGGalleryMenuWindow extends PopupWindow {
30 |
31 | private Context mContext;
32 |
33 | private MenuAdapter mAdapter;
34 |
35 | private RecyclerView mMenuRecyclerView;
36 |
37 | private List mItemModels;
38 |
39 | private LayoutInflater mLayoutInflater;
40 |
41 | public IMGGalleryMenuWindow(Context context) {
42 | super(context);
43 | mContext = context;
44 |
45 | View contentView = getLayoutInflater().inflate(
46 | R.layout.image_layout_gallery_pop, null, false);
47 |
48 | setContentView(contentView);
49 |
50 | setWidth(WindowManager.LayoutParams.MATCH_PARENT);
51 | DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
52 | setHeight(Math.round(metrics.heightPixels * 0.76f));
53 |
54 | setOutsideTouchable(true);
55 | setBackgroundDrawable(new ColorDrawable(0x60000000));
56 |
57 | mAdapter = new MenuAdapter();
58 | mMenuRecyclerView = contentView.findViewById(R.id.image_rv_menu);
59 | mMenuRecyclerView.setAdapter(mAdapter);
60 | }
61 |
62 | private LayoutInflater getLayoutInflater() {
63 | if (mLayoutInflater == null) {
64 | mLayoutInflater = LayoutInflater.from(mContext);
65 | }
66 | return mLayoutInflater;
67 | }
68 |
69 | public void setMenuItems(List menuItems) {
70 | if (menuItems != null) {
71 | if (mItemModels == null) {
72 | mItemModels = new ArrayList<>();
73 | }
74 | mItemModels.clear();
75 |
76 | GalleryMenuItemViewModel _all = null;
77 | for (String item : menuItems) {
78 | GalleryMenuItemViewModel model = new GalleryMenuItemViewModel(
79 | null, item, IMGScanner.ALL_IMAGES.equals(item)
80 | );
81 | if (IMGScanner.ALL_IMAGES.equals(item)) {
82 | _all = model;
83 | }
84 | mItemModels.add(model);
85 | }
86 |
87 | ensureSingleItemSelected(_all);
88 | }
89 | }
90 |
91 | private void ensureSingleItemSelected(GalleryMenuItemViewModel model) {
92 | if (mItemModels != null) {
93 | for (GalleryMenuItemViewModel m : mItemModels) {
94 | m.isSelected = m == model;
95 | }
96 | }
97 | }
98 |
99 | private void onItemSelected(int position) {
100 | GalleryMenuItemViewModel item = mAdapter.getItem(position);
101 | if (item != null) {
102 | ensureSingleItemSelected(item);
103 | mAdapter.notifyDataSetChanged();
104 | }
105 | }
106 |
107 | public void show(View parent) {
108 | showAsDropDown(parent, 0, 0);
109 | }
110 |
111 | private class MenuAdapter extends RecyclerView.Adapter
112 | implements IMGViewHolderCallback {
113 |
114 | @Override
115 | public MenuItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
116 | return new MenuItemViewHolder(getLayoutInflater().inflate(
117 | R.layout.image_layout_gallery_menu_item, parent, false), this);
118 | }
119 |
120 | @Override
121 | public void onBindViewHolder(MenuItemViewHolder holder, int position) {
122 | holder.update(getItem(position));
123 | }
124 |
125 | public GalleryMenuItemViewModel getItem(int position) {
126 | if (position >= 0 && position < getItemCount()) {
127 | return mItemModels.get(position);
128 | }
129 | return null;
130 | }
131 |
132 | @Override
133 | public int getItemCount() {
134 | return mItemModels == null ? 0 : mItemModels.size();
135 | }
136 |
137 | @Override
138 | public void onViewHolderClick(RecyclerView.ViewHolder holder) {
139 | onItemSelected(holder.getAdapterPosition());
140 | }
141 | }
142 |
143 | private static class MenuItemViewHolder extends RecyclerView.ViewHolder
144 | implements View.OnClickListener {
145 |
146 | private SimpleDraweeView imageView;
147 |
148 | private TextView textView;
149 |
150 | private RadioButton radioButton;
151 |
152 | private IMGViewHolderCallback callback;
153 |
154 | public MenuItemViewHolder(View itemView, IMGViewHolderCallback callback) {
155 | super(itemView);
156 |
157 | this.callback = callback;
158 |
159 | imageView = itemView.findViewById(R.id.sdv_image);
160 | textView = itemView.findViewById(R.id.tv_name);
161 | radioButton = itemView.findViewById(R.id.rb_select);
162 |
163 | itemView.setOnClickListener(this);
164 | }
165 |
166 | public void update(GalleryMenuItemViewModel model) {
167 | textView.setText(model.name);
168 | radioButton.setChecked(model.isSelected);
169 | }
170 |
171 | @Override
172 | public void onClick(View v) {
173 | if (callback != null) {
174 | callback.onViewHolderClick(this);
175 | }
176 | }
177 | }
178 |
179 | public static class GalleryMenuItemViewModel {
180 |
181 | private Uri uri;
182 |
183 | private String name;
184 |
185 | private boolean isSelected;
186 |
187 | public GalleryMenuItemViewModel(Uri uri, String name, boolean isSelected) {
188 | this.uri = uri;
189 | this.name = name;
190 | this.isSelected = isSelected;
191 | }
192 | }
193 | }
194 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/core/clip/IMGClipWindow.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.core.clip;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.Color;
5 | import android.graphics.Matrix;
6 | import android.graphics.Paint;
7 | import android.graphics.Path;
8 | import android.graphics.RectF;
9 |
10 | import me.minetsh.imaging.core.util.IMGUtils;
11 |
12 | /**
13 | * Created by felix on 2017/11/29 下午5:41.
14 | */
15 |
16 | public class IMGClipWindow implements IMGClip {
17 |
18 | /**
19 | * 裁剪区域
20 | */
21 | private RectF mFrame = new RectF();
22 |
23 | private RectF mBaseFrame = new RectF();
24 |
25 | private RectF mTargetFrame = new RectF();
26 |
27 | /**
28 | * 裁剪窗口
29 | */
30 | private RectF mWinFrame = new RectF();
31 |
32 | private RectF mWin = new RectF();
33 |
34 | private float[] mCells = new float[16];
35 |
36 | private float[] mCorners = new float[32];
37 |
38 | private float[][] mBaseSizes = new float[2][4];
39 |
40 | /**
41 | * 是否在裁剪中
42 | */
43 | private boolean isClipping = false;
44 |
45 | private boolean isResetting = true;
46 |
47 | private boolean isShowShade = false;
48 |
49 | private boolean isHoming = false;
50 |
51 | private Matrix M = new Matrix();
52 |
53 | private Path mShadePath = new Path();
54 |
55 | private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
56 |
57 | /**
58 | * 垂直窗口比例
59 | */
60 | private static final float VERTICAL_RATIO = 0.8f;
61 |
62 | private static final int COLOR_CELL = 0x80FFFFFF;
63 |
64 | private static final int COLOR_FRAME = Color.WHITE;
65 |
66 | private static final int COLOR_CORNER = Color.WHITE;
67 |
68 | private static final int COLOR_SHADE = 0xCC000000;
69 |
70 | {
71 | mPaint.setStyle(Paint.Style.STROKE);
72 | mPaint.setStrokeCap(Paint.Cap.SQUARE);
73 | }
74 |
75 | public IMGClipWindow() {
76 |
77 | }
78 |
79 | /**
80 | * 计算裁剪窗口区域
81 | */
82 | public void setClipWinSize(float width, float height) {
83 | mWin.set(0, 0, width, height);
84 | mWinFrame.set(0, 0, width, height * VERTICAL_RATIO);
85 |
86 | if (!mFrame.isEmpty()) {
87 | IMGUtils.center(mWinFrame, mFrame);
88 | mTargetFrame.set(mFrame);
89 | }
90 | }
91 |
92 | public void reset(RectF clipImage, float rotate) {
93 | RectF imgRect = new RectF();
94 | M.setRotate(rotate, clipImage.centerX(), clipImage.centerY());
95 | M.mapRect(imgRect, clipImage);
96 | reset(imgRect.width(), imgRect.height());
97 | }
98 |
99 | /**
100 | * 重置裁剪
101 | */
102 | private void reset(float clipWidth, float clipHeight) {
103 | setResetting(true);
104 | mFrame.set(0, 0, clipWidth, clipHeight);
105 | IMGUtils.fitCenter(mWinFrame, mFrame, CLIP_MARGIN);
106 | mTargetFrame.set(mFrame);
107 | }
108 |
109 | public boolean homing() {
110 | mBaseFrame.set(mFrame);
111 | mTargetFrame.set(mFrame);
112 | IMGUtils.fitCenter(mWinFrame, mTargetFrame, CLIP_MARGIN);
113 | return isHoming = !mTargetFrame.equals(mBaseFrame);
114 | }
115 |
116 | public void homing(float fraction) {
117 | if (isHoming) {
118 | mFrame.set(
119 | mBaseFrame.left + (mTargetFrame.left - mBaseFrame.left) * fraction,
120 | mBaseFrame.top + (mTargetFrame.top - mBaseFrame.top) * fraction,
121 | mBaseFrame.right + (mTargetFrame.right - mBaseFrame.right) * fraction,
122 | mBaseFrame.bottom + (mTargetFrame.bottom - mBaseFrame.bottom) * fraction
123 | );
124 | }
125 | }
126 |
127 | public boolean isHoming() {
128 | return isHoming;
129 | }
130 |
131 | public void setHoming(boolean homing) {
132 | isHoming = homing;
133 | }
134 |
135 | public boolean isClipping() {
136 | return isClipping;
137 | }
138 |
139 | public void setClipping(boolean clipping) {
140 | isClipping = clipping;
141 | }
142 |
143 | public boolean isResetting() {
144 | return isResetting;
145 | }
146 |
147 | public void setResetting(boolean resetting) {
148 | isResetting = resetting;
149 | }
150 |
151 | public RectF getFrame() {
152 | return mFrame;
153 | }
154 |
155 | public RectF getWinFrame() {
156 | return mWinFrame;
157 | }
158 |
159 | public RectF getOffsetFrame(float offsetX, float offsetY) {
160 | RectF frame = new RectF(mFrame);
161 | frame.offset(offsetX, offsetY);
162 | return frame;
163 | }
164 |
165 | public RectF getTargetFrame() {
166 | return mTargetFrame;
167 | }
168 |
169 | public RectF getOffsetTargetFrame(float offsetX, float offsetY) {
170 | RectF targetFrame = new RectF(mFrame);
171 | targetFrame.offset(offsetX, offsetY);
172 | return targetFrame;
173 | }
174 |
175 | public boolean isShowShade() {
176 | return isShowShade;
177 | }
178 |
179 | public void setShowShade(boolean showShade) {
180 | isShowShade = showShade;
181 | }
182 |
183 | public void onDraw(Canvas canvas) {
184 |
185 | if (isResetting) {
186 | return;
187 | }
188 |
189 | float[] size = {mFrame.width(), mFrame.height()};
190 | for (int i = 0; i < mBaseSizes.length; i++) {
191 | for (int j = 0; j < mBaseSizes[i].length; j++) {
192 | mBaseSizes[i][j] = size[i] * CLIP_SIZE_RATIO[j];
193 | }
194 | }
195 |
196 | for (int i = 0; i < mCells.length; i++) {
197 | mCells[i] = mBaseSizes[i & 1][CLIP_CELL_STRIDES >>> (i << 1) & 3];
198 | }
199 |
200 | for (int i = 0; i < mCorners.length; i++) {
201 | mCorners[i] = mBaseSizes[i & 1][CLIP_CORNER_STRIDES >>> i & 1]
202 | + CLIP_CORNER_SIZES[CLIP_CORNERS[i] & 3] + CLIP_CORNER_STEPS[CLIP_CORNERS[i] >> 2];
203 | }
204 |
205 | canvas.translate(mFrame.left, mFrame.top);
206 | mPaint.setStyle(Paint.Style.STROKE);
207 | mPaint.setColor(COLOR_CELL);
208 | mPaint.setStrokeWidth(CLIP_THICKNESS_CELL);
209 | canvas.drawLines(mCells, mPaint);
210 |
211 | canvas.translate(-mFrame.left, -mFrame.top);
212 | mPaint.setColor(COLOR_FRAME);
213 | mPaint.setStrokeWidth(CLIP_THICKNESS_FRAME);
214 | canvas.drawRect(mFrame, mPaint);
215 |
216 | canvas.translate(mFrame.left, mFrame.top);
217 | mPaint.setColor(COLOR_CORNER);
218 | mPaint.setStrokeWidth(CLIP_THICKNESS_SEWING);
219 | canvas.drawLines(mCorners, mPaint);
220 | }
221 |
222 | public void onDrawShade(Canvas canvas) {
223 | if (!isShowShade) return;
224 |
225 | // 计算遮罩图形
226 | mShadePath.reset();
227 |
228 | mShadePath.setFillType(Path.FillType.WINDING);
229 | mShadePath.addRect(mFrame.left + 100, mFrame.top + 100, mFrame.right - 100, mFrame.bottom - 100, Path.Direction.CW);
230 |
231 | mPaint.setColor(COLOR_SHADE);
232 | mPaint.setStyle(Paint.Style.FILL);
233 | canvas.drawPath(mShadePath, mPaint);
234 | }
235 |
236 | public Anchor getAnchor(float x, float y) {
237 | if (Anchor.isCohesionContains(mFrame, -CLIP_CORNER_SIZE, x, y)
238 | && !Anchor.isCohesionContains(mFrame, CLIP_CORNER_SIZE, x, y)) {
239 | int v = 0;
240 | float[] cohesion = Anchor.cohesion(mFrame, 0);
241 | float[] pos = {x, y};
242 | for (int i = 0; i < cohesion.length; i++) {
243 | if (Math.abs(cohesion[i] - pos[i >> 1]) < CLIP_CORNER_SIZE) {
244 | v |= 1 << i;
245 | }
246 | }
247 |
248 | Anchor anchor = Anchor.valueOf(v);
249 | if (anchor != null) {
250 | isHoming = false;
251 | }
252 | return anchor;
253 | }
254 | return null;
255 | }
256 |
257 | public void onScroll(Anchor anchor, float dx, float dy) {
258 | anchor.move(mWinFrame, mFrame, dx, dy);
259 | }
260 | }
261 |
--------------------------------------------------------------------------------
/image/src/main/res/layout/image_edit_opt_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
28 |
29 |
41 |
42 |
43 |
44 |
48 |
49 |
57 |
58 |
63 |
64 |
72 |
73 |
80 |
81 |
86 |
87 |
93 |
94 |
99 |
100 |
105 |
106 |
111 |
112 |
113 |
114 |
121 |
122 |
123 |
124 |
132 |
133 |
134 |
135 |
143 |
144 |
152 |
153 |
156 |
157 |
167 |
168 |
171 |
172 |
180 |
181 |
184 |
185 |
195 |
196 |
197 |
198 |
--------------------------------------------------------------------------------
/image/src/main/java/me/minetsh/imaging/view/IMGStickerView.java:
--------------------------------------------------------------------------------
1 | package me.minetsh.imaging.view;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.Matrix;
7 | import android.graphics.Paint;
8 | import android.graphics.Rect;
9 | import android.graphics.RectF;
10 | import android.util.AttributeSet;
11 | import android.view.MotionEvent;
12 | import android.view.View;
13 | import android.view.ViewConfiguration;
14 | import android.view.ViewGroup;
15 | import android.widget.ImageView;
16 |
17 | import me.minetsh.imaging.R;
18 | import me.minetsh.imaging.core.sticker.IMGSticker;
19 | import me.minetsh.imaging.core.sticker.IMGStickerAdjustHelper;
20 | import me.minetsh.imaging.core.sticker.IMGStickerHelper;
21 | import me.minetsh.imaging.core.sticker.IMGStickerMoveHelper;
22 |
23 | /**
24 | * Created by felix on 2017/12/12 下午4:26.
25 | */
26 |
27 | public abstract class IMGStickerView extends ViewGroup implements IMGSticker, View.OnClickListener {
28 |
29 | private static final String TAG = "IMGStickerView";
30 |
31 | private View mContentView;
32 |
33 | private float mScale = 1f;
34 |
35 | // TODO
36 | private int mDownShowing = 0;
37 |
38 | private IMGStickerMoveHelper mMoveHelper;
39 |
40 | private IMGStickerHelper mStickerHelper;
41 |
42 | private ImageView mRemoveView, mAdjustView;
43 |
44 | private float mMaxScaleValue = MAX_SCALE_VALUE;
45 |
46 | private Paint PAINT;
47 |
48 | private Matrix mMatrix = new Matrix();
49 |
50 | private RectF mFrame = new RectF();
51 |
52 | private Rect mTempFrame = new Rect();
53 |
54 | private static final float MAX_SCALE_VALUE = 4f;
55 |
56 | private static final int ANCHOR_SIZE = 48;
57 |
58 | private static final int ANCHOR_SIZE_HALF = ANCHOR_SIZE >> 1;
59 |
60 | private static final float STROKE_WIDTH = 3f;
61 |
62 | {
63 | PAINT = new Paint(Paint.ANTI_ALIAS_FLAG);
64 | PAINT.setColor(Color.WHITE);
65 | PAINT.setStyle(Paint.Style.STROKE);
66 | PAINT.setStrokeWidth(STROKE_WIDTH);
67 | }
68 |
69 | public IMGStickerView(Context context) {
70 | this(context, null, 0);
71 | }
72 |
73 | public IMGStickerView(Context context, AttributeSet attrs) {
74 | this(context, attrs, 0);
75 | }
76 |
77 | public IMGStickerView(Context context, AttributeSet attrs, int defStyleAttr) {
78 | super(context, attrs, defStyleAttr);
79 | onInitialize(context);
80 | }
81 |
82 | public void onInitialize(Context context) {
83 | setBackgroundColor(Color.TRANSPARENT);
84 |
85 | mContentView = onCreateContentView(context);
86 | addView(mContentView, getContentLayoutParams());
87 |
88 | mRemoveView = new ImageView(context);
89 | mRemoveView.setScaleType(ImageView.ScaleType.FIT_XY);
90 | mRemoveView.setImageResource(R.mipmap.image_ic_delete);
91 | addView(mRemoveView, getAnchorLayoutParams());
92 | mRemoveView.setOnClickListener(this);
93 |
94 | mAdjustView = new ImageView(context);
95 | mAdjustView.setScaleType(ImageView.ScaleType.FIT_XY);
96 | mAdjustView.setImageResource(R.mipmap.image_ic_adjust);
97 | addView(mAdjustView, getAnchorLayoutParams());
98 |
99 | new IMGStickerAdjustHelper(this, mAdjustView);
100 |
101 | mStickerHelper = new IMGStickerHelper<>(this);
102 | mMoveHelper = new IMGStickerMoveHelper(this);
103 | }
104 |
105 | public abstract View onCreateContentView(Context context);
106 |
107 | @Override
108 | public float getScale() {
109 | return mScale;
110 | }
111 |
112 | @Override
113 | public void setScale(float scale) {
114 | mScale = scale;
115 |
116 | mContentView.setScaleX(mScale);
117 | mContentView.setScaleY(mScale);
118 |
119 | int pivotX = (getLeft() + getRight()) >> 1;
120 | int pivotY = (getTop() + getBottom()) >> 1;
121 |
122 | mFrame.set(pivotX, pivotY, pivotX, pivotY);
123 | mFrame.inset(-(mContentView.getMeasuredWidth() >> 1), -(mContentView.getMeasuredHeight() >> 1));
124 |
125 | mMatrix.setScale(mScale, mScale, mFrame.centerX(), mFrame.centerY());
126 | mMatrix.mapRect(mFrame);
127 |
128 | mFrame.round(mTempFrame);
129 |
130 | layout(mTempFrame.left, mTempFrame.top, mTempFrame.right, mTempFrame.bottom);
131 | }
132 |
133 | @Override
134 | public void addScale(float scale) {
135 | setScale(getScale() * scale);
136 | }
137 |
138 | private LayoutParams getContentLayoutParams() {
139 | return new LayoutParams(
140 | LayoutParams.WRAP_CONTENT,
141 | LayoutParams.WRAP_CONTENT
142 | );
143 | }
144 |
145 | private LayoutParams getAnchorLayoutParams() {
146 | return new LayoutParams(ANCHOR_SIZE, ANCHOR_SIZE);
147 | }
148 |
149 | @Override
150 | public void draw(Canvas canvas) {
151 | if (isShowing()) {
152 | canvas.drawRect(ANCHOR_SIZE_HALF, ANCHOR_SIZE_HALF,
153 | getWidth() - ANCHOR_SIZE_HALF,
154 | getHeight() - ANCHOR_SIZE_HALF, PAINT);
155 | }
156 | super.draw(canvas);
157 | }
158 |
159 | @Override
160 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
161 | int count = getChildCount();
162 |
163 | int maxHeight = 0;
164 | int maxWidth = 0;
165 | int childState = 0;
166 |
167 | for (int i = 0; i < count; i++) {
168 | final View child = getChildAt(i);
169 | if (child.getVisibility() != GONE) {
170 | child.measure(widthMeasureSpec, heightMeasureSpec);
171 |
172 | maxWidth = Math.round(Math.max(maxWidth, child.getMeasuredWidth() * child.getScaleX()));
173 | maxHeight = Math.round(Math.max(maxHeight, child.getMeasuredHeight() * child.getScaleY()));
174 |
175 | childState = combineMeasuredStates(childState, child.getMeasuredState());
176 | }
177 | }
178 |
179 | maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
180 | maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
181 |
182 | setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
183 | resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));
184 | }
185 |
186 | @Override
187 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
188 |
189 | mFrame.set(left, top, right, bottom);
190 |
191 | int count = getChildCount();
192 | if (count == 0) {
193 | return;
194 | }
195 |
196 | mRemoveView.layout(0, 0, mRemoveView.getMeasuredWidth(), mRemoveView.getMeasuredHeight());
197 | mAdjustView.layout(
198 | right - left - mAdjustView.getMeasuredWidth(),
199 | bottom - top - mAdjustView.getMeasuredHeight(),
200 | right - left, bottom - top
201 | );
202 |
203 | int centerX = (right - left) >> 1, centerY = (bottom - top) >> 1;
204 | int hw = mContentView.getMeasuredWidth() >> 1;
205 | int hh = mContentView.getMeasuredHeight() >> 1;
206 |
207 | mContentView.layout(centerX - hw, centerY - hh, centerX + hw, centerY + hh);
208 | }
209 |
210 | @Override
211 | protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
212 | return isShowing() && super.drawChild(canvas, child, drawingTime);
213 | }
214 |
215 | @Override
216 | public boolean onInterceptTouchEvent(MotionEvent ev) {
217 | if (!isShowing() && ev.getAction() == MotionEvent.ACTION_DOWN) {
218 | mDownShowing = 0;
219 | show();
220 | return true;
221 | }
222 | return isShowing() && super.onInterceptTouchEvent(ev);
223 | }
224 |
225 | @Override
226 | public boolean onTouchEvent(MotionEvent event) {
227 |
228 | boolean handled = mMoveHelper.onTouch(this, event);
229 |
230 | switch (event.getActionMasked()) {
231 | case MotionEvent.ACTION_DOWN:
232 | mDownShowing++;
233 | break;
234 | case MotionEvent.ACTION_UP:
235 | if (mDownShowing > 1 && event.getEventTime() - event.getDownTime() < ViewConfiguration.getTapTimeout()) {
236 | onContentTap();
237 | return true;
238 | }
239 | break;
240 | }
241 |
242 | return handled | super.onTouchEvent(event);
243 | }
244 |
245 | @Override
246 | public void onClick(View v) {
247 | if (v == mRemoveView) {
248 | onRemove();
249 | }
250 | }
251 |
252 | public void onRemove() {
253 | mStickerHelper.remove();
254 | }
255 |
256 | public void onContentTap() {
257 |
258 | }
259 |
260 | @Override
261 | public boolean show() {
262 | return mStickerHelper.show();
263 | }
264 |
265 | @Override
266 | public boolean remove() {
267 | return mStickerHelper.remove();
268 | }
269 |
270 | @Override
271 | public boolean dismiss() {
272 | return mStickerHelper.dismiss();
273 | }
274 |
275 | @Override
276 | public boolean isShowing() {
277 | return mStickerHelper.isShowing();
278 | }
279 |
280 | @Override
281 | public RectF getFrame() {
282 | return mStickerHelper.getFrame();
283 | }
284 |
285 | @Override
286 | public void onSticker(Canvas canvas) {
287 | canvas.translate(mContentView.getX(), mContentView.getY());
288 | mContentView.draw(canvas);
289 | }
290 |
291 | @Override
292 | public void registerCallback(Callback callback) {
293 | mStickerHelper.registerCallback(callback);
294 | }
295 |
296 | @Override
297 | public void unregisterCallback(Callback callback) {
298 | mStickerHelper.unregisterCallback(callback);
299 | }
300 | }
301 |
--------------------------------------------------------------------------------