Repository: jdamcd/android-crop
Branch: master
Commit: f4b2d25a0508
Files: 63
Total size: 101.6 KB
Directory structure:
gitextract_a6ryjm71/
├── .gitignore
├── .publishing/
│ └── sonatype.gradle
├── .travis.yml
├── CHANGELOG.md
├── README.md
├── build.gradle
├── example/
│ ├── build.gradle
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── soundcloud/
│ │ └── android/
│ │ └── crop/
│ │ └── example/
│ │ └── MainActivity.java
│ └── res/
│ ├── drawable/
│ │ └── texture.xml
│ ├── layout/
│ │ └── activity_main.xml
│ ├── menu/
│ │ └── activity_main.xml
│ └── values/
│ ├── colors.xml
│ ├── strings.xml
│ └── theme.xml
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── lib/
│ ├── build.gradle
│ └── src/
│ ├── androidTest/
│ │ └── java/
│ │ └── com/
│ │ └── soundcloud/
│ │ └── android/
│ │ └── crop/
│ │ ├── BaseTestCase.java
│ │ └── CropBuilderTest.java
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── soundcloud/
│ │ └── android/
│ │ └── crop/
│ │ ├── Crop.java
│ │ ├── CropImageActivity.java
│ │ ├── CropImageView.java
│ │ ├── CropUtil.java
│ │ ├── HighlightView.java
│ │ ├── ImageViewTouchBase.java
│ │ ├── Log.java
│ │ ├── MonitoredActivity.java
│ │ └── RotateBitmap.java
│ └── res/
│ ├── drawable/
│ │ ├── crop__selectable_background.xml
│ │ └── crop__texture.xml
│ ├── drawable-v21/
│ │ └── crop__selectable_background.xml
│ ├── layout/
│ │ ├── crop__activity_crop.xml
│ │ └── crop__layout_done_cancel.xml
│ ├── values/
│ │ ├── attrs.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ ├── values-ar/
│ │ └── strings.xml
│ ├── values-ca/
│ │ └── strings.xml
│ ├── values-de/
│ │ └── strings.xml
│ ├── values-es/
│ │ └── strings.xml
│ ├── values-fa/
│ │ └── strings.xml
│ ├── values-fr/
│ │ └── strings.xml
│ ├── values-in/
│ │ └── strings.xml
│ ├── values-it/
│ │ └── strings.xml
│ ├── values-ja/
│ │ └── strings.xml
│ ├── values-ko/
│ │ └── strings.xml
│ ├── values-land/
│ │ └── dimens.xml
│ ├── values-large/
│ │ └── dimens.xml
│ ├── values-pt/
│ │ └── strings.xml
│ ├── values-ru/
│ │ └── strings.xml
│ ├── values-sv/
│ │ └── strings.xml
│ ├── values-tr/
│ │ └── strings.xml
│ ├── values-v21/
│ │ └── colors.xml
│ ├── values-zh-rCN/
│ │ └── strings.xml
│ └── values-zh-rTW/
│ └── strings.xml
└── settings.gradle
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
/local.properties
/.idea
build/
.gradle
.DS_Store
*.iml
================================================
FILE: .publishing/sonatype.gradle
================================================
configurations {
archives {
extendsFrom configurations.default
}
}
signing {
required { has("release") && gradle.taskGraph.hasTask("uploadArchives") }
sign configurations.archives
}
uploadArchives {
configuration = configurations.archives
repositories {
mavenDeployer {
beforeDeployment {
MavenDeployment deployment -> signing.signPom(deployment)
}
repository(url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2') {
authentication(userName: sonatypeUsername, password: sonatypePassword)
}
pom.project {
name 'Android Crop'
packaging 'aar'
description 'An Android library that provides an image cropping Activity'
url 'https://github.com/jdamcd/android-crop'
scm {
url 'scm:git@github.com:jdamcd/android-crop.git'
connection 'scm:git@github.com:jdamcd/android-crop.git'
developerConnection 'scm:git@github.com:jdamcd/android-crop.git'
}
licenses {
license {
name 'The Apache Software License, Version 2.0'
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
organization {
name 'SoundCloud'
url 'http://developers.soundcloud.com'
}
developers {
developer {
id 'jdamcd'
name 'Jamie McDonald'
email 'mcdonald@soundcloud.com'
}
}
}
}
}
}
================================================
FILE: .travis.yml
================================================
language: android
sudo: false
android:
components:
- build-tools-23.0.1
- android-23
- extra-android-support
- extra-android-m2repository
script:
- ./gradlew clean build
================================================
FILE: CHANGELOG.md
================================================
## Next
* Fix max size crash when input cannot be decoded
* Translations: German, Chinese (simplified & traditional)
## 1.0.1
* Support image picker helper from Fragments
* Restore support for SDK level 10
* Fix translucent status bar set via app theme
* Fix wrong result code when crop results in IOException
* Fix image "twitching" on zoom out to max bounds
* Translations: Italian, Turkish, Catalan, Swedish
## 1.0.0
* Improved builder interface: `Crop.of(in, out).start(activity)`
* Material styling
* Drop support for SDK level 9
* Start crop from support Fragment
* Fix max size
* Fix issue cropping images from Google Drive
* Optional circle crop guide
* Optional custom request code
* Translations: French, Korean, Chinese, Spanish, Japanese, Arabic, Portuguese, Indonesian, Russian
## 0.9.10
* Fix bug on some devices where image was displayed with 0 size
## 0.9.9
* Downscale source images that are too big to load
* Optional always show crop handles
* Fix shading outside crop area on some API levels
================================================
FILE: README.md
================================================
> I guess people are just cropping out all the sadness
An Android library project that provides a simple image cropping `Activity`, based on code from AOSP.
**Deprecated!** This project is not maintained. If it doesn't meet your needs as is, consider creating a fork or picking from these [alternatives](https://android-arsenal.com/tag/45).
[](http://search.maven.org/#artifactdetails%7Ccom.soundcloud.android%7Candroid-crop%7C1.0.1%7Caar.asc)
[](CHANGELOG.md)
## Features
* Gradle build & AAR
* Modern UI
* Backwards compatible to SDK 10
* Simple builder for configuration
* Example project
## Usage
First, declare `CropImageActivity` in your manifest file:
```xml
<activity android:name="com.soundcloud.android.crop.CropImageActivity" />
```
#### Crop
```java
Crop.of(inputUri, outputUri).asSquare().start(activity)
```
Listen for the result of the crop (see example project if you want to do some error handling):
```java
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent result) {
if (requestCode == Crop.REQUEST_CROP && resultCode == RESULT_OK) {
doSomethingWithCroppedImage(outputUri);
}
}
```
Some attributes are provided to customise the crop screen. See the example project [theme](https://github.com/jdamcd/android-crop/blob/master/example/src/main/res/values/theme.xml).
#### Pick
The library provides a utility method to start an image picker:
```java
Crop.pickImage(activity)
```
#### Dependency
The AAR is published on Maven Central:
```groovy
compile 'com.soundcloud.android:android-crop:1.0.1@aar'
```
## How does it look?

## License
This project is based on the [AOSP](https://source.android.com) camera image cropper via [android-cropimage](https://github.com/lvillani/android-cropimage).
Copyright 2016 SoundCloud
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: build.gradle
================================================
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.1.0'
}
}
allprojects {
group = 'com.soundcloud.android'
version = project.VERSION
repositories {
mavenCentral()
}
}
================================================
FILE: example/build.gradle
================================================
apply plugin: 'com.android.application'
archivesBaseName = 'android-crop-example'
android {
compileSdkVersion 23
buildToolsVersion '23.0.1'
defaultConfig {
minSdkVersion 10
targetSdkVersion 22
versionCode Integer.parseInt(project.VERSION_CODE)
versionName project.VERSION
}
}
dependencies {
compile project(':lib')
}
================================================
FILE: example/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.soundcloud.android.crop.example" >
<application
android:label="@string/app_name"
android:icon="@drawable/ic_launcher"
android:allowBackup="true"
android:theme="@style/CustomTheme">
<activity
android:name="com.soundcloud.android.crop.example.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.soundcloud.android.crop.CropImageActivity" />
</application>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
</manifest>
================================================
FILE: example/src/main/java/com/soundcloud/android/crop/example/MainActivity.java
================================================
package com.soundcloud.android.crop.example;
import com.soundcloud.android.crop.Crop;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ImageView;
import android.widget.Toast;
import java.io.File;
public class MainActivity extends Activity {
private ImageView resultView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
resultView = (ImageView) findViewById(R.id.result_image);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_select) {
resultView.setImageDrawable(null);
Crop.pickImage(this);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent result) {
if (requestCode == Crop.REQUEST_PICK && resultCode == RESULT_OK) {
beginCrop(result.getData());
} else if (requestCode == Crop.REQUEST_CROP) {
handleCrop(resultCode, result);
}
}
private void beginCrop(Uri source) {
Uri destination = Uri.fromFile(new File(getCacheDir(), "cropped"));
Crop.of(source, destination).asSquare().start(this);
}
private void handleCrop(int resultCode, Intent result) {
if (resultCode == RESULT_OK) {
resultView.setImageURI(Crop.getOutput(result));
} else if (resultCode == Crop.RESULT_ERROR) {
Toast.makeText(this, Crop.getError(result).getMessage(), Toast.LENGTH_SHORT).show();
}
}
}
================================================
FILE: example/src/main/res/drawable/texture.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/tile"
android:tileMode="repeat" />
================================================
FILE: example/src/main/res/layout/activity_main.xml
================================================
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.soundcloud.android.crop.example.MainActivity">
<ImageView
android:id="@+id/result_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:background="@drawable/texture" />
</FrameLayout>
================================================
FILE: example/src/main/res/menu/activity_main.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/action_select"
android:title="@string/action_select"
android:showAsAction="always" />
</menu>
================================================
FILE: example/src/main/res/values/colors.xml
================================================
<resources>
<color name="highlight">#f3f3f3</color>
</resources>
================================================
FILE: example/src/main/res/values/strings.xml
================================================
<resources>
<string name="app_name">Crop Demo</string>
<string name="action_select">Pick</string>
</resources>
================================================
FILE: example/src/main/res/values/theme.xml
================================================
<resources xmlns:tools="http://schemas.android.com/tools">
<style tools:ignore="NewApi" name="CustomTheme" parent="android:Theme.DeviceDefault">
<item name="cropImageStyle">@style/Widget.CropImageView</item>
</style>
<style name="Widget.CropImageView" parent="android:Widget">
<item name="showThirds">true</item>
<item name="showCircle">false</item>
<item name="showHandles">always</item>
<item name="highlightColor">@color/highlight</item>
</style>
</resources>
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Fri May 27 11:24:52 EDT 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
================================================
FILE: gradle.properties
================================================
VERSION=1.0.1
VERSION_CODE=1
signing.keyId=63A46540
signing.secretKeyRingFile=
signing.password=
sonatypeUsername=jdamcd
sonatypePassword=
================================================
FILE: gradlew
================================================
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: lib/build.gradle
================================================
apply plugin: 'com.android.library'
apply plugin: 'maven'
apply plugin: 'signing'
//apply from: '../.publishing/sonatype.gradle'
archivesBaseName = 'android-crop'
android {
compileSdkVersion 23
buildToolsVersion '23.0.1'
defaultConfig {
minSdkVersion 10
targetSdkVersion 22
testApplicationId 'com.soundcloud.android.crop.test'
testInstrumentationRunner 'android.test.InstrumentationTestRunner'
}
}
dependencies {
compile 'com.android.support:support-annotations:23.0.1'
compile 'com.android.support:support-v4:23.0.1'
androidTestCompile 'com.squareup:fest-android:1.0.7'
androidTestCompile 'com.android.support:support-v4:23.0.1'
androidTestCompile 'org.mockito:mockito-core:1.9.5'
androidTestCompile 'com.google.dexmaker:dexmaker:1.0'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.0'
}
================================================
FILE: lib/src/androidTest/java/com/soundcloud/android/crop/BaseTestCase.java
================================================
package com.soundcloud.android.crop;
import android.test.InstrumentationTestCase;
public class BaseTestCase extends InstrumentationTestCase {
@Override
public void setUp() throws Exception {
super.setUp();
// Work around dexmaker issue when running tests on Android 4.3
System.setProperty("dexmaker.dexcache",
getInstrumentation().getTargetContext().getCacheDir().getPath());
}
}
================================================
FILE: lib/src/androidTest/java/com/soundcloud/android/crop/CropBuilderTest.java
================================================
package com.soundcloud.android.crop;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.fest.assertions.api.ANDROID;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.provider.MediaStore;
public class CropBuilderTest extends BaseTestCase {
private Activity activity;
private Crop builder;
@Override
public void setUp() throws Exception {
super.setUp();
activity = mock(Activity.class);
when(activity.getPackageName()).thenReturn("com.example");
builder = Crop.of(Uri.parse("image:input"), Uri.parse("image:output"));
}
public void testInputUriSetAsData() {
ANDROID.assertThat(builder.getIntent(activity)).hasData("image:input");
}
public void testOutputUriSetAsExtra() {
Intent intent = builder.getIntent(activity);
Uri output = intent.getParcelableExtra(MediaStore.EXTRA_OUTPUT);
assertThat(output.toString()).isEqualTo("image:output");
}
public void testAspectRatioSetAsExtras() {
builder.withAspect(16, 10);
Intent intent = builder.getIntent(activity);
assertThat(intent.getIntExtra("aspect_x", 0)).isEqualTo(16);
assertThat(intent.getIntExtra("aspect_y", 0)).isEqualTo(10);
}
public void testFixedAspectRatioSetAsExtras() {
builder.asSquare();
Intent intent = builder.getIntent(activity);
assertThat(intent.getIntExtra("aspect_x", 0)).isEqualTo(1);
assertThat(intent.getIntExtra("aspect_y", 0)).isEqualTo(1);
}
public void testMaxSizeSetAsExtras() {
builder.withMaxSize(400, 300);
Intent intent = builder.getIntent(activity);
assertThat(intent.getIntExtra("max_x", 0)).isEqualTo(400);
assertThat(intent.getIntExtra("max_y", 0)).isEqualTo(300);
}
public void testBuildsIntentWithMultipleOptions() {
builder.asSquare().withMaxSize(200, 200);
Intent intent = builder.getIntent(activity);
assertThat(intent.getIntExtra("aspect_x", 0)).isEqualTo(1);
assertThat(intent.getIntExtra("aspect_y", 0)).isEqualTo(1);
assertThat(intent.getIntExtra("max_x", 0)).isEqualTo(200);
assertThat(intent.getIntExtra("max_y", 0)).isEqualTo(200);
}
public void testAsPngSetAsExtras() {
builder.asPng(true);
Intent intent = builder.getIntent(activity);
assertThat(intent.getBooleanExtra("as_png", false)).isEqualTo(true);
}
}
================================================
FILE: lib/src/main/AndroidManifest.xml
================================================
<manifest package="com.soundcloud.android.crop" />
================================================
FILE: lib/src/main/java/com/soundcloud/android/crop/Crop.java
================================================
package com.soundcloud.android.crop;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Fragment;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;
import android.widget.Toast;
/**
* Builder for crop Intents and utils for handling result
*/
public class Crop {
public static final int REQUEST_CROP = 6709;
public static final int REQUEST_PICK = 9162;
public static final int RESULT_ERROR = 404;
interface Extra {
String ASPECT_X = "aspect_x";
String ASPECT_Y = "aspect_y";
String MAX_X = "max_x";
String MAX_Y = "max_y";
String AS_PNG = "as_png";
String ERROR = "error";
}
private Intent cropIntent;
/**
* Create a crop Intent builder with source and destination image Uris
*
* @param source Uri for image to crop
* @param destination Uri for saving the cropped image
*/
public static Crop of(Uri source, Uri destination) {
return new Crop(source, destination);
}
private Crop(Uri source, Uri destination) {
cropIntent = new Intent();
cropIntent.setData(source);
cropIntent.putExtra(MediaStore.EXTRA_OUTPUT, destination);
}
/**
* Set fixed aspect ratio for crop area
*
* @param x Aspect X
* @param y Aspect Y
*/
public Crop withAspect(int x, int y) {
cropIntent.putExtra(Extra.ASPECT_X, x);
cropIntent.putExtra(Extra.ASPECT_Y, y);
return this;
}
/**
* Crop area with fixed 1:1 aspect ratio
*/
public Crop asSquare() {
cropIntent.putExtra(Extra.ASPECT_X, 1);
cropIntent.putExtra(Extra.ASPECT_Y, 1);
return this;
}
/**
* Set maximum crop size
*
* @param width Max width
* @param height Max height
*/
public Crop withMaxSize(int width, int height) {
cropIntent.putExtra(Extra.MAX_X, width);
cropIntent.putExtra(Extra.MAX_Y, height);
return this;
}
/**
* Set whether to save the result as a PNG or not. Helpful to preserve alpha.
* @param asPng whether to save the result as a PNG or not
*/
public Crop asPng(boolean asPng) {
cropIntent.putExtra(Extra.AS_PNG, asPng);
return this;
}
/**
* Send the crop Intent from an Activity
*
* @param activity Activity to receive result
*/
public void start(Activity activity) {
start(activity, REQUEST_CROP);
}
/**
* Send the crop Intent from an Activity with a custom request code
*
* @param activity Activity to receive result
* @param requestCode requestCode for result
*/
public void start(Activity activity, int requestCode) {
activity.startActivityForResult(getIntent(activity), requestCode);
}
/**
* Send the crop Intent from a Fragment
*
* @param context Context
* @param fragment Fragment to receive result
*/
public void start(Context context, Fragment fragment) {
start(context, fragment, REQUEST_CROP);
}
/**
* Send the crop Intent from a support library Fragment
*
* @param context Context
* @param fragment Fragment to receive result
*/
public void start(Context context, android.support.v4.app.Fragment fragment) {
start(context, fragment, REQUEST_CROP);
}
/**
* Send the crop Intent with a custom request code
*
* @param context Context
* @param fragment Fragment to receive result
* @param requestCode requestCode for result
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void start(Context context, Fragment fragment, int requestCode) {
fragment.startActivityForResult(getIntent(context), requestCode);
}
/**
* Send the crop Intent with a custom request code
*
* @param context Context
* @param fragment Fragment to receive result
* @param requestCode requestCode for result
*/
public void start(Context context, android.support.v4.app.Fragment fragment, int requestCode) {
fragment.startActivityForResult(getIntent(context), requestCode);
}
/**
* Get Intent to start crop Activity
*
* @param context Context
* @return Intent for CropImageActivity
*/
public Intent getIntent(Context context) {
cropIntent.setClass(context, CropImageActivity.class);
return cropIntent;
}
/**
* Retrieve URI for cropped image, as set in the Intent builder
*
* @param result Output Image URI
*/
public static Uri getOutput(Intent result) {
return result.getParcelableExtra(MediaStore.EXTRA_OUTPUT);
}
/**
* Retrieve error that caused crop to fail
*
* @param result Result Intent
* @return Throwable handled in CropImageActivity
*/
public static Throwable getError(Intent result) {
return (Throwable) result.getSerializableExtra(Extra.ERROR);
}
/**
* Pick image from an Activity
*
* @param activity Activity to receive result
*/
public static void pickImage(Activity activity) {
pickImage(activity, REQUEST_PICK);
}
/**
* Pick image from a Fragment
*
* @param context Context
* @param fragment Fragment to receive result
*/
public static void pickImage(Context context, Fragment fragment) {
pickImage(context, fragment, REQUEST_PICK);
}
/**
* Pick image from a support library Fragment
*
* @param context Context
* @param fragment Fragment to receive result
*/
public static void pickImage(Context context, android.support.v4.app.Fragment fragment) {
pickImage(context, fragment, REQUEST_PICK);
}
/**
* Pick image from an Activity with a custom request code
*
* @param activity Activity to receive result
* @param requestCode requestCode for result
*/
public static void pickImage(Activity activity, int requestCode) {
try {
activity.startActivityForResult(getImagePicker(), requestCode);
} catch (ActivityNotFoundException e) {
showImagePickerError(activity);
}
}
/**
* Pick image from a Fragment with a custom request code
*
* @param context Context
* @param fragment Fragment to receive result
* @param requestCode requestCode for result
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static void pickImage(Context context, Fragment fragment, int requestCode) {
try {
fragment.startActivityForResult(getImagePicker(), requestCode);
} catch (ActivityNotFoundException e) {
showImagePickerError(context);
}
}
/**
* Pick image from a support library Fragment with a custom request code
*
* @param context Context
* @param fragment Fragment to receive result
* @param requestCode requestCode for result
*/
public static void pickImage(Context context, android.support.v4.app.Fragment fragment, int requestCode) {
try {
fragment.startActivityForResult(getImagePicker(), requestCode);
} catch (ActivityNotFoundException e) {
showImagePickerError(context);
}
}
private static Intent getImagePicker() {
return new Intent(Intent.ACTION_GET_CONTENT).setType("image/*");
}
private static void showImagePickerError(Context context) {
Toast.makeText(context.getApplicationContext(), R.string.crop__pick_error, Toast.LENGTH_SHORT).show();
}
}
================================================
FILE: lib/src/main/java/com/soundcloud/android/crop/CropImageActivity.java
================================================
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.soundcloud.android.crop;
import android.annotation.TargetApi;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.net.Uri;
import android.opengl.GLES10;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.provider.MediaStore;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.CountDownLatch;
/*
* Modified from original in AOSP.
*/
public class CropImageActivity extends MonitoredActivity {
private static final int SIZE_DEFAULT = 2048;
private static final int SIZE_LIMIT = 4096;
private final Handler handler = new Handler();
private int aspectX;
private int aspectY;
// Output image
private int maxX;
private int maxY;
private int exifRotation;
private boolean saveAsPng;
private Uri sourceUri;
private Uri saveUri;
private boolean isSaving;
private int sampleSize;
private RotateBitmap rotateBitmap;
private CropImageView imageView;
private HighlightView cropView;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setupWindowFlags();
setupViews();
loadInput();
if (rotateBitmap == null) {
finish();
return;
}
startCrop();
}
@TargetApi(Build.VERSION_CODES.KITKAT)
private void setupWindowFlags() {
requestWindowFeature(Window.FEATURE_NO_TITLE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
}
private void setupViews() {
setContentView(R.layout.crop__activity_crop);
imageView = (CropImageView) findViewById(R.id.crop_image);
imageView.context = this;
imageView.setRecycler(new ImageViewTouchBase.Recycler() {
@Override
public void recycle(Bitmap b) {
b.recycle();
System.gc();
}
});
findViewById(R.id.btn_cancel).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
setResult(RESULT_CANCELED);
finish();
}
});
findViewById(R.id.btn_done).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
onSaveClicked();
}
});
}
private void loadInput() {
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras != null) {
aspectX = extras.getInt(Crop.Extra.ASPECT_X);
aspectY = extras.getInt(Crop.Extra.ASPECT_Y);
maxX = extras.getInt(Crop.Extra.MAX_X);
maxY = extras.getInt(Crop.Extra.MAX_Y);
saveAsPng = extras.getBoolean(Crop.Extra.AS_PNG, false);
saveUri = extras.getParcelable(MediaStore.EXTRA_OUTPUT);
}
sourceUri = intent.getData();
if (sourceUri != null) {
exifRotation = CropUtil.getExifRotation(CropUtil.getFromMediaUri(this, getContentResolver(), sourceUri));
InputStream is = null;
try {
sampleSize = calculateBitmapSampleSize(sourceUri);
is = getContentResolver().openInputStream(sourceUri);
BitmapFactory.Options option = new BitmapFactory.Options();
option.inSampleSize = sampleSize;
rotateBitmap = new RotateBitmap(BitmapFactory.decodeStream(is, null, option), exifRotation);
} catch (IOException e) {
Log.e("Error reading image: " + e.getMessage(), e);
setResultException(e);
} catch (OutOfMemoryError e) {
Log.e("OOM reading image: " + e.getMessage(), e);
setResultException(e);
} finally {
CropUtil.closeSilently(is);
}
}
}
private int calculateBitmapSampleSize(Uri bitmapUri) throws IOException {
InputStream is = null;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
try {
is = getContentResolver().openInputStream(bitmapUri);
BitmapFactory.decodeStream(is, null, options); // Just get image size
} finally {
CropUtil.closeSilently(is);
}
int maxSize = getMaxImageSize();
int sampleSize = 1;
while (options.outHeight / sampleSize > maxSize || options.outWidth / sampleSize > maxSize) {
sampleSize = sampleSize << 1;
}
return sampleSize;
}
private int getMaxImageSize() {
int textureLimit = getMaxTextureSize();
if (textureLimit == 0) {
return SIZE_DEFAULT;
} else {
return Math.min(textureLimit, SIZE_LIMIT);
}
}
private int getMaxTextureSize() {
// The OpenGL texture size is the maximum size that can be drawn in an ImageView
int[] maxSize = new int[1];
GLES10.glGetIntegerv(GLES10.GL_MAX_TEXTURE_SIZE, maxSize, 0);
return maxSize[0];
}
private void startCrop() {
if (isFinishing()) {
return;
}
imageView.setImageRotateBitmapResetBase(rotateBitmap, true);
CropUtil.startBackgroundJob(this, null, getResources().getString(R.string.crop__wait),
new Runnable() {
public void run() {
final CountDownLatch latch = new CountDownLatch(1);
handler.post(new Runnable() {
public void run() {
if (imageView.getScale() == 1F) {
imageView.center();
}
latch.countDown();
}
});
try {
latch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Cropper().crop();
}
}, handler
);
}
private class Cropper {
private void makeDefault() {
if (rotateBitmap == null) {
return;
}
HighlightView hv = new HighlightView(imageView);
final int width = rotateBitmap.getWidth();
final int height = rotateBitmap.getHeight();
Rect imageRect = new Rect(0, 0, width, height);
// Make the default size about 4/5 of the width or height
int cropWidth = Math.min(width, height) * 4 / 5;
@SuppressWarnings("SuspiciousNameCombination")
int cropHeight = cropWidth;
if (aspectX != 0 && aspectY != 0) {
if (aspectX > aspectY) {
cropHeight = cropWidth * aspectY / aspectX;
} else {
cropWidth = cropHeight * aspectX / aspectY;
}
}
int x = (width - cropWidth) / 2;
int y = (height - cropHeight) / 2;
RectF cropRect = new RectF(x, y, x + cropWidth, y + cropHeight);
hv.setup(imageView.getUnrotatedMatrix(), imageRect, cropRect, aspectX != 0 && aspectY != 0);
imageView.add(hv);
}
public void crop() {
handler.post(new Runnable() {
public void run() {
makeDefault();
imageView.invalidate();
if (imageView.highlightViews.size() == 1) {
cropView = imageView.highlightViews.get(0);
cropView.setFocus(true);
}
}
});
}
}
private void onSaveClicked() {
if (cropView == null || isSaving) {
return;
}
isSaving = true;
Bitmap croppedImage;
Rect r = cropView.getScaledCropRect(sampleSize);
int width = r.width();
int height = r.height();
int outWidth = width;
int outHeight = height;
if (maxX > 0 && maxY > 0 && (width > maxX || height > maxY)) {
float ratio = (float) width / (float) height;
if ((float) maxX / (float) maxY > ratio) {
outHeight = maxY;
outWidth = (int) ((float) maxY * ratio + .5f);
} else {
outWidth = maxX;
outHeight = (int) ((float) maxX / ratio + .5f);
}
}
try {
croppedImage = decodeRegionCrop(r, outWidth, outHeight);
} catch (IllegalArgumentException e) {
setResultException(e);
finish();
return;
}
if (croppedImage != null) {
imageView.setImageRotateBitmapResetBase(new RotateBitmap(croppedImage, exifRotation), true);
imageView.center();
imageView.highlightViews.clear();
}
saveImage(croppedImage);
}
private void saveImage(Bitmap croppedImage) {
if (croppedImage != null) {
final Bitmap b = croppedImage;
CropUtil.startBackgroundJob(this, null, getResources().getString(R.string.crop__saving),
new Runnable() {
public void run() {
saveOutput(b);
}
}, handler
);
} else {
finish();
}
}
private Bitmap decodeRegionCrop(Rect rect, int outWidth, int outHeight) {
// Release memory now
clearImageView();
InputStream is = null;
Bitmap croppedImage = null;
try {
is = getContentResolver().openInputStream(sourceUri);
BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
final int width = decoder.getWidth();
final int height = decoder.getHeight();
if (exifRotation != 0) {
// Adjust crop area to account for image rotation
Matrix matrix = new Matrix();
matrix.setRotate(-exifRotation);
RectF adjusted = new RectF();
matrix.mapRect(adjusted, new RectF(rect));
// Adjust to account for origin at 0,0
adjusted.offset(adjusted.left < 0 ? width : 0, adjusted.top < 0 ? height : 0);
rect = new Rect((int) adjusted.left, (int) adjusted.top, (int) adjusted.right, (int) adjusted.bottom);
}
try {
croppedImage = decoder.decodeRegion(rect, new BitmapFactory.Options());
if (croppedImage != null && (rect.width() > outWidth || rect.height() > outHeight)) {
Matrix matrix = new Matrix();
matrix.postScale((float) outWidth / rect.width(), (float) outHeight / rect.height());
croppedImage = Bitmap.createBitmap(croppedImage, 0, 0, croppedImage.getWidth(), croppedImage.getHeight(), matrix, true);
}
} catch (IllegalArgumentException e) {
// Rethrow with some extra information
throw new IllegalArgumentException("Rectangle " + rect + " is outside of the image ("
+ width + "," + height + "," + exifRotation + ")", e);
}
} catch (IOException e) {
Log.e("Error cropping image: " + e.getMessage(), e);
setResultException(e);
} catch (OutOfMemoryError e) {
Log.e("OOM cropping image: " + e.getMessage(), e);
setResultException(e);
} finally {
CropUtil.closeSilently(is);
}
return croppedImage;
}
private void clearImageView() {
imageView.clear();
if (rotateBitmap != null) {
rotateBitmap.recycle();
}
System.gc();
}
private void saveOutput(Bitmap croppedImage) {
if (saveUri != null) {
OutputStream outputStream = null;
try {
outputStream = getContentResolver().openOutputStream(saveUri);
if (outputStream != null) {
croppedImage.compress(saveAsPng ? Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG,
90, // note: quality is ignored when using PNG
outputStream);
}
} catch (IOException e) {
setResultException(e);
Log.e("Cannot open file: " + saveUri, e);
} finally {
CropUtil.closeSilently(outputStream);
}
CropUtil.copyExifRotation(
CropUtil.getFromMediaUri(this, getContentResolver(), sourceUri),
CropUtil.getFromMediaUri(this, getContentResolver(), saveUri)
);
setResultUri(saveUri);
}
final Bitmap b = croppedImage;
handler.post(new Runnable() {
public void run() {
imageView.clear();
b.recycle();
}
});
finish();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (rotateBitmap != null) {
rotateBitmap.recycle();
}
}
@Override
public boolean onSearchRequested() {
return false;
}
public boolean isSaving() {
return isSaving;
}
private void setResultUri(Uri uri) {
setResult(RESULT_OK, new Intent().putExtra(MediaStore.EXTRA_OUTPUT, uri));
}
private void setResultException(Throwable throwable) {
setResult(Crop.RESULT_ERROR, new Intent().putExtra(Crop.Extra.ERROR, throwable));
}
}
================================================
FILE: lib/src/main/java/com/soundcloud/android/crop/CropImageView.java
================================================
package com.soundcloud.android.crop;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.MotionEvent;
import java.util.ArrayList;
public class CropImageView extends ImageViewTouchBase {
ArrayList<HighlightView> highlightViews = new ArrayList<HighlightView>();
HighlightView motionHighlightView;
Context context;
private float lastX;
private float lastY;
private int motionEdge;
private int validPointerId;
public CropImageView(Context context) {
super(context);
}
public CropImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CropImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (bitmapDisplayed.getBitmap() != null) {
for (HighlightView hv : highlightViews) {
hv.matrix.set(getUnrotatedMatrix());
hv.invalidate();
if (hv.hasFocus()) {
centerBasedOnHighlightView(hv);
}
}
}
}
@Override
protected void zoomTo(float scale, float centerX, float centerY) {
super.zoomTo(scale, centerX, centerY);
for (HighlightView hv : highlightViews) {
hv.matrix.set(getUnrotatedMatrix());
hv.invalidate();
}
}
@Override
protected void zoomIn() {
super.zoomIn();
for (HighlightView hv : highlightViews) {
hv.matrix.set(getUnrotatedMatrix());
hv.invalidate();
}
}
@Override
protected void zoomOut() {
super.zoomOut();
for (HighlightView hv : highlightViews) {
hv.matrix.set(getUnrotatedMatrix());
hv.invalidate();
}
}
@Override
protected void postTranslate(float deltaX, float deltaY) {
super.postTranslate(deltaX, deltaY);
for (HighlightView hv : highlightViews) {
hv.matrix.postTranslate(deltaX, deltaY);
hv.invalidate();
}
}
@Override
public boolean onTouchEvent(@NonNull MotionEvent event) {
CropImageActivity cropImageActivity = (CropImageActivity) context;
if (cropImageActivity.isSaving()) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
for (HighlightView hv : highlightViews) {
int edge = hv.getHit(event.getX(), event.getY());
if (edge != HighlightView.GROW_NONE) {
motionEdge = edge;
motionHighlightView = hv;
lastX = event.getX();
lastY = event.getY();
// Prevent multiple touches from interfering with crop area re-sizing
validPointerId = event.getPointerId(event.getActionIndex());
motionHighlightView.setMode((edge == HighlightView.MOVE)
? HighlightView.ModifyMode.Move
: HighlightView.ModifyMode.Grow);
break;
}
}
break;
case MotionEvent.ACTION_UP:
if (motionHighlightView != null) {
centerBasedOnHighlightView(motionHighlightView);
motionHighlightView.setMode(HighlightView.ModifyMode.None);
}
motionHighlightView = null;
center();
break;
case MotionEvent.ACTION_MOVE:
if (motionHighlightView != null && event.getPointerId(event.getActionIndex()) == validPointerId) {
motionHighlightView.handleMotion(motionEdge, event.getX()
- lastX, event.getY() - lastY);
lastX = event.getX();
lastY = event.getY();
}
// If we're not zoomed then there's no point in even allowing the user to move the image around.
// This call to center puts it back to the normalized location.
if (getScale() == 1F) {
center();
}
break;
}
return true;
}
// Pan the displayed image to make sure the cropping rectangle is visible.
private void ensureVisible(HighlightView hv) {
Rect r = hv.drawRect;
int panDeltaX1 = Math.max(0, getLeft() - r.left);
int panDeltaX2 = Math.min(0, getRight() - r.right);
int panDeltaY1 = Math.max(0, getTop() - r.top);
int panDeltaY2 = Math.min(0, getBottom() - r.bottom);
int panDeltaX = panDeltaX1 != 0 ? panDeltaX1 : panDeltaX2;
int panDeltaY = panDeltaY1 != 0 ? panDeltaY1 : panDeltaY2;
if (panDeltaX != 0 || panDeltaY != 0) {
panBy(panDeltaX, panDeltaY);
}
}
// If the cropping rectangle's size changed significantly, change the
// view's center and scale according to the cropping rectangle.
private void centerBasedOnHighlightView(HighlightView hv) {
Rect drawRect = hv.drawRect;
float width = drawRect.width();
float height = drawRect.height();
float thisWidth = getWidth();
float thisHeight = getHeight();
float z1 = thisWidth / width * .6F;
float z2 = thisHeight / height * .6F;
float zoom = Math.min(z1, z2);
zoom = zoom * this.getScale();
zoom = Math.max(1F, zoom);
if ((Math.abs(zoom - getScale()) / zoom) > .1) {
float[] coordinates = new float[] { hv.cropRect.centerX(), hv.cropRect.centerY() };
getUnrotatedMatrix().mapPoints(coordinates);
zoomTo(zoom, coordinates[0], coordinates[1], 300F);
}
ensureVisible(hv);
}
@Override
protected void onDraw(@NonNull Canvas canvas) {
super.onDraw(canvas);
for (HighlightView highlightView : highlightViews) {
highlightView.draw(canvas);
}
}
public void add(HighlightView hv) {
highlightViews.add(hv);
invalidate();
}
}
================================================
FILE: lib/src/main/java/com/soundcloud/android/crop/CropUtil.java
================================================
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.soundcloud.android.crop;
import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Handler;
import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import java.io.Closeable;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/*
* Modified from original in AOSP.
*/
class CropUtil {
private static final String SCHEME_FILE = "file";
private static final String SCHEME_CONTENT = "content";
public static void closeSilently(@Nullable Closeable c) {
if (c == null) return;
try {
c.close();
} catch (Throwable t) {
// Do nothing
}
}
public static int getExifRotation(File imageFile) {
if (imageFile == null) return 0;
try {
ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
// We only recognize a subset of orientation tag values
switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)) {
case ExifInterface.ORIENTATION_ROTATE_90:
return 90;
case ExifInterface.ORIENTATION_ROTATE_180:
return 180;
case ExifInterface.ORIENTATION_ROTATE_270:
return 270;
default:
return ExifInterface.ORIENTATION_UNDEFINED;
}
} catch (IOException e) {
Log.e("Error getting Exif data", e);
return 0;
}
}
public static boolean copyExifRotation(File sourceFile, File destFile) {
if (sourceFile == null || destFile == null) return false;
try {
ExifInterface exifSource = new ExifInterface(sourceFile.getAbsolutePath());
ExifInterface exifDest = new ExifInterface(destFile.getAbsolutePath());
exifDest.setAttribute(ExifInterface.TAG_ORIENTATION, exifSource.getAttribute(ExifInterface.TAG_ORIENTATION));
exifDest.saveAttributes();
return true;
} catch (IOException e) {
Log.e("Error copying Exif data", e);
return false;
}
}
@Nullable
public static File getFromMediaUri(Context context, ContentResolver resolver, Uri uri) {
if (uri == null) return null;
if (SCHEME_FILE.equals(uri.getScheme())) {
return new File(uri.getPath());
} else if (SCHEME_CONTENT.equals(uri.getScheme())) {
final String[] filePathColumn = { MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DISPLAY_NAME };
Cursor cursor = null;
try {
cursor = resolver.query(uri, filePathColumn, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
final int columnIndex = (uri.toString().startsWith("content://com.google.android.gallery3d")) ?
cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME) :
cursor.getColumnIndex(MediaStore.MediaColumns.DATA);
// Picasa images on API 13+
if (columnIndex != -1) {
String filePath = cursor.getString(columnIndex);
if (!TextUtils.isEmpty(filePath)) {
return new File(filePath);
}
}
}
} catch (IllegalArgumentException e) {
// Google Drive images
return getFromMediaUriPfd(context, resolver, uri);
} catch (SecurityException ignored) {
// Nothing we can do
} finally {
if (cursor != null) cursor.close();
}
}
return null;
}
private static String getTempFilename(Context context) throws IOException {
File outputDir = context.getCacheDir();
File outputFile = File.createTempFile("image", "tmp", outputDir);
return outputFile.getAbsolutePath();
}
@Nullable
private static File getFromMediaUriPfd(Context context, ContentResolver resolver, Uri uri) {
if (uri == null) return null;
FileInputStream input = null;
FileOutputStream output = null;
try {
ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r");
FileDescriptor fd = pfd.getFileDescriptor();
input = new FileInputStream(fd);
String tempFilename = getTempFilename(context);
output = new FileOutputStream(tempFilename);
int read;
byte[] bytes = new byte[4096];
while ((read = input.read(bytes)) != -1) {
output.write(bytes, 0, read);
}
return new File(tempFilename);
} catch (IOException ignored) {
// Nothing we can do
} finally {
closeSilently(input);
closeSilently(output);
}
return null;
}
public static void startBackgroundJob(MonitoredActivity activity,
String title, String message, Runnable job, Handler handler) {
// Make the progress dialog uncancelable, so that we can guarantee
// the thread will be done before the activity getting destroyed
ProgressDialog dialog = ProgressDialog.show(
activity, title, message, true, false);
new Thread(new BackgroundJob(activity, job, dialog, handler)).start();
}
private static class BackgroundJob extends MonitoredActivity.LifeCycleAdapter implements Runnable {
private final MonitoredActivity activity;
private final ProgressDialog dialog;
private final Runnable job;
private final Handler handler;
private final Runnable cleanupRunner = new Runnable() {
public void run() {
activity.removeLifeCycleListener(BackgroundJob.this);
if (dialog.getWindow() != null) dialog.dismiss();
}
};
public BackgroundJob(MonitoredActivity activity, Runnable job,
ProgressDialog dialog, Handler handler) {
this.activity = activity;
this.dialog = dialog;
this.job = job;
this.activity.addLifeCycleListener(this);
this.handler = handler;
}
public void run() {
try {
job.run();
} finally {
handler.post(cleanupRunner);
}
}
@Override
public void onActivityDestroyed(MonitoredActivity activity) {
// We get here only when the onDestroyed being called before
// the cleanupRunner. So, run it now and remove it from the queue
cleanupRunner.run();
handler.removeCallbacks(cleanupRunner);
}
@Override
public void onActivityStopped(MonitoredActivity activity) {
dialog.hide();
}
@Override
public void onActivityStarted(MonitoredActivity activity) {
dialog.show();
}
}
}
================================================
FILE: lib/src/main/java/com/soundcloud/android/crop/HighlightView.java
================================================
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.soundcloud.android.crop;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.os.Build;
import android.util.TypedValue;
import android.view.View;
/*
* Modified from version in AOSP.
*
* This class is used to display a highlighted cropping rectangle
* overlayed on the image. There are two coordinate spaces in use. One is
* image, another is screen. computeLayout() uses matrix to map from image
* space to screen space.
*/
class HighlightView {
public static final int GROW_NONE = (1 << 0);
public static final int GROW_LEFT_EDGE = (1 << 1);
public static final int GROW_RIGHT_EDGE = (1 << 2);
public static final int GROW_TOP_EDGE = (1 << 3);
public static final int GROW_BOTTOM_EDGE = (1 << 4);
public static final int MOVE = (1 << 5);
private static final int DEFAULT_HIGHLIGHT_COLOR = 0xFF33B5E5;
private static final float HANDLE_RADIUS_DP = 12f;
private static final float OUTLINE_DP = 2f;
enum ModifyMode { None, Move, Grow }
enum HandleMode { Changing, Always, Never }
RectF cropRect; // Image space
Rect drawRect; // Screen space
Matrix matrix;
private RectF imageRect; // Image space
private final Paint outsidePaint = new Paint();
private final Paint outlinePaint = new Paint();
private final Paint handlePaint = new Paint();
private View viewContext; // View displaying image
private boolean showThirds;
private boolean showCircle;
private int highlightColor;
private ModifyMode modifyMode = ModifyMode.None;
private HandleMode handleMode = HandleMode.Changing;
private boolean maintainAspectRatio;
private float initialAspectRatio;
private float handleRadius;
private float outlineWidth;
private boolean isFocused;
public HighlightView(View context) {
viewContext = context;
initStyles(context.getContext());
}
private void initStyles(Context context) {
TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.cropImageStyle, outValue, true);
TypedArray attributes = context.obtainStyledAttributes(outValue.resourceId, R.styleable.CropImageView);
try {
showThirds = attributes.getBoolean(R.styleable.CropImageView_showThirds, false);
showCircle = attributes.getBoolean(R.styleable.CropImageView_showCircle, false);
highlightColor = attributes.getColor(R.styleable.CropImageView_highlightColor,
DEFAULT_HIGHLIGHT_COLOR);
handleMode = HandleMode.values()[attributes.getInt(R.styleable.CropImageView_showHandles, 0)];
} finally {
attributes.recycle();
}
}
public void setup(Matrix m, Rect imageRect, RectF cropRect, boolean maintainAspectRatio) {
matrix = new Matrix(m);
this.cropRect = cropRect;
this.imageRect = new RectF(imageRect);
this.maintainAspectRatio = maintainAspectRatio;
initialAspectRatio = this.cropRect.width() / this.cropRect.height();
drawRect = computeLayout();
outsidePaint.setARGB(125, 50, 50, 50);
outlinePaint.setStyle(Paint.Style.STROKE);
outlinePaint.setAntiAlias(true);
outlineWidth = dpToPx(OUTLINE_DP);
handlePaint.setColor(highlightColor);
handlePaint.setStyle(Paint.Style.FILL);
handlePaint.setAntiAlias(true);
handleRadius = dpToPx(HANDLE_RADIUS_DP);
modifyMode = ModifyMode.None;
}
private float dpToPx(float dp) {
return dp * viewContext.getResources().getDisplayMetrics().density;
}
protected void draw(Canvas canvas) {
canvas.save();
Path path = new Path();
outlinePaint.setStrokeWidth(outlineWidth);
if (!hasFocus()) {
outlinePaint.setColor(Color.BLACK);
canvas.drawRect(drawRect, outlinePaint);
} else {
Rect viewDrawingRect = new Rect();
viewContext.getDrawingRect(viewDrawingRect);
path.addRect(new RectF(drawRect), Path.Direction.CW);
outlinePaint.setColor(highlightColor);
if (isClipPathSupported(canvas)) {
canvas.clipPath(path, Region.Op.DIFFERENCE);
canvas.drawRect(viewDrawingRect, outsidePaint);
} else {
drawOutsideFallback(canvas);
}
canvas.restore();
canvas.drawPath(path, outlinePaint);
if (showThirds) {
drawThirds(canvas);
}
if (showCircle) {
drawCircle(canvas);
}
if (handleMode == HandleMode.Always ||
(handleMode == HandleMode.Changing && modifyMode == ModifyMode.Grow)) {
drawHandles(canvas);
}
}
}
/*
* Fall back to naive method for darkening outside crop area
*/
private void drawOutsideFallback(Canvas canvas) {
canvas.drawRect(0, 0, canvas.getWidth(), drawRect.top, outsidePaint);
canvas.drawRect(0, drawRect.bottom, canvas.getWidth(), canvas.getHeight(), outsidePaint);
canvas.drawRect(0, drawRect.top, drawRect.left, drawRect.bottom, outsidePaint);
canvas.drawRect(drawRect.right, drawRect.top, canvas.getWidth(), drawRect.bottom, outsidePaint);
}
/*
* Clip path is broken, unreliable or not supported on:
* - JellyBean MR1
* - ICS & ICS MR1 with hardware acceleration turned on
*/
@SuppressLint("NewApi")
private boolean isClipPathSupported(Canvas canvas) {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) {
return false;
} else if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH)
|| Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
return true;
} else {
return !canvas.isHardwareAccelerated();
}
}
private void drawHandles(Canvas canvas) {
int xMiddle = drawRect.left + ((drawRect.right - drawRect.left) / 2);
int yMiddle = drawRect.top + ((drawRect.bottom - drawRect.top) / 2);
canvas.drawCircle(drawRect.left, yMiddle, handleRadius, handlePaint);
canvas.drawCircle(xMiddle, drawRect.top, handleRadius, handlePaint);
canvas.drawCircle(drawRect.right, yMiddle, handleRadius, handlePaint);
canvas.drawCircle(xMiddle, drawRect.bottom, handleRadius, handlePaint);
}
private void drawThirds(Canvas canvas) {
outlinePaint.setStrokeWidth(1);
float xThird = (drawRect.right - drawRect.left) / 3;
float yThird = (drawRect.bottom - drawRect.top) / 3;
canvas.drawLine(drawRect.left + xThird, drawRect.top,
drawRect.left + xThird, drawRect.bottom, outlinePaint);
canvas.drawLine(drawRect.left + xThird * 2, drawRect.top,
drawRect.left + xThird * 2, drawRect.bottom, outlinePaint);
canvas.drawLine(drawRect.left, drawRect.top + yThird,
drawRect.right, drawRect.top + yThird, outlinePaint);
canvas.drawLine(drawRect.left, drawRect.top + yThird * 2,
drawRect.right, drawRect.top + yThird * 2, outlinePaint);
}
private void drawCircle(Canvas canvas) {
outlinePaint.setStrokeWidth(1);
canvas.drawOval(new RectF(drawRect), outlinePaint);
}
public void setMode(ModifyMode mode) {
if (mode != modifyMode) {
modifyMode = mode;
viewContext.invalidate();
}
}
// Determines which edges are hit by touching at (x, y)
public int getHit(float x, float y) {
Rect r = computeLayout();
final float hysteresis = 20F;
int retval = GROW_NONE;
// verticalCheck makes sure the position is between the top and
// the bottom edge (with some tolerance). Similar for horizCheck.
boolean verticalCheck = (y >= r.top - hysteresis)
&& (y < r.bottom + hysteresis);
boolean horizCheck = (x >= r.left - hysteresis)
&& (x < r.right + hysteresis);
// Check whether the position is near some edge(s)
if ((Math.abs(r.left - x) < hysteresis) && verticalCheck) {
retval |= GROW_LEFT_EDGE;
}
if ((Math.abs(r.right - x) < hysteresis) && verticalCheck) {
retval |= GROW_RIGHT_EDGE;
}
if ((Math.abs(r.top - y) < hysteresis) && horizCheck) {
retval |= GROW_TOP_EDGE;
}
if ((Math.abs(r.bottom - y) < hysteresis) && horizCheck) {
retval |= GROW_BOTTOM_EDGE;
}
// Not near any edge but inside the rectangle: move
if (retval == GROW_NONE && r.contains((int) x, (int) y)) {
retval = MOVE;
}
return retval;
}
// Handles motion (dx, dy) in screen space.
// The "edge" parameter specifies which edges the user is dragging.
void handleMotion(int edge, float dx, float dy) {
Rect r = computeLayout();
if (edge == MOVE) {
// Convert to image space before sending to moveBy()
moveBy(dx * (cropRect.width() / r.width()),
dy * (cropRect.height() / r.height()));
} else {
if (((GROW_LEFT_EDGE | GROW_RIGHT_EDGE) & edge) == 0) {
dx = 0;
}
if (((GROW_TOP_EDGE | GROW_BOTTOM_EDGE) & edge) == 0) {
dy = 0;
}
// Convert to image space before sending to growBy()
float xDelta = dx * (cropRect.width() / r.width());
float yDelta = dy * (cropRect.height() / r.height());
growBy((((edge & GROW_LEFT_EDGE) != 0) ? -1 : 1) * xDelta,
(((edge & GROW_TOP_EDGE) != 0) ? -1 : 1) * yDelta);
}
}
// Grows the cropping rectangle by (dx, dy) in image space
void moveBy(float dx, float dy) {
Rect invalRect = new Rect(drawRect);
cropRect.offset(dx, dy);
// Put the cropping rectangle inside image rectangle
cropRect.offset(
Math.max(0, imageRect.left - cropRect.left),
Math.max(0, imageRect.top - cropRect.top));
cropRect.offset(
Math.min(0, imageRect.right - cropRect.right),
Math.min(0, imageRect.bottom - cropRect.bottom));
drawRect = computeLayout();
invalRect.union(drawRect);
invalRect.inset(-(int) handleRadius, -(int) handleRadius);
viewContext.invalidate(invalRect);
}
// Grows the cropping rectangle by (dx, dy) in image space.
void growBy(float dx, float dy) {
if (maintainAspectRatio) {
if (dx != 0) {
dy = dx / initialAspectRatio;
} else if (dy != 0) {
dx = dy * initialAspectRatio;
}
}
// Don't let the cropping rectangle grow too fast.
// Grow at most half of the difference between the image rectangle and
// the cropping rectangle.
RectF r = new RectF(cropRect);
if (dx > 0F && r.width() + 2 * dx > imageRect.width()) {
dx = (imageRect.width() - r.width()) / 2F;
if (maintainAspectRatio) {
dy = dx / initialAspectRatio;
}
}
if (dy > 0F && r.height() + 2 * dy > imageRect.height()) {
dy = (imageRect.height() - r.height()) / 2F;
if (maintainAspectRatio) {
dx = dy * initialAspectRatio;
}
}
r.inset(-dx, -dy);
// Don't let the cropping rectangle shrink too fast
final float widthCap = 25F;
if (r.width() < widthCap) {
r.inset(-(widthCap - r.width()) / 2F, 0F);
}
float heightCap = maintainAspectRatio
? (widthCap / initialAspectRatio)
: widthCap;
if (r.height() < heightCap) {
r.inset(0F, -(heightCap - r.height()) / 2F);
}
// Put the cropping rectangle inside the image rectangle
if (r.left < imageRect.left) {
r.offset(imageRect.left - r.left, 0F);
} else if (r.right > imageRect.right) {
r.offset(-(r.right - imageRect.right), 0F);
}
if (r.top < imageRect.top) {
r.offset(0F, imageRect.top - r.top);
} else if (r.bottom > imageRect.bottom) {
r.offset(0F, -(r.bottom - imageRect.bottom));
}
cropRect.set(r);
drawRect = computeLayout();
viewContext.invalidate();
}
// Returns the cropping rectangle in image space with specified scale
public Rect getScaledCropRect(float scale) {
return new Rect((int) (cropRect.left * scale), (int) (cropRect.top * scale),
(int) (cropRect.right * scale), (int) (cropRect.bottom * scale));
}
// Maps the cropping rectangle from image space to screen space
private Rect computeLayout() {
RectF r = new RectF(cropRect.left, cropRect.top,
cropRect.right, cropRect.bottom);
matrix.mapRect(r);
return new Rect(Math.round(r.left), Math.round(r.top),
Math.round(r.right), Math.round(r.bottom));
}
public void invalidate() {
drawRect = computeLayout();
}
public boolean hasFocus() {
return isFocused;
}
public void setFocus(boolean isFocused) {
this.isFocused = isFocused;
}
}
================================================
FILE: lib/src/main/java/com/soundcloud/android/crop/ImageViewTouchBase.java
================================================
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.soundcloud.android.crop;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.widget.ImageView;
/*
* Modified from original in AOSP.
*/
abstract class ImageViewTouchBase extends ImageView {
private static final float SCALE_RATE = 1.25F;
// This is the base transformation which is used to show the image
// initially. The current computation for this shows the image in
// it's entirety, letterboxing as needed. One could choose to
// show the image as cropped instead.
//
// This matrix is recomputed when we go from the thumbnail image to
// the full size image.
protected Matrix baseMatrix = new Matrix();
// This is the supplementary transformation which reflects what
// the user has done in terms of zooming and panning.
//
// This matrix remains the same when we go from the thumbnail image
// to the full size image.
protected Matrix suppMatrix = new Matrix();
// This is the final matrix which is computed as the concatentation
// of the base matrix and the supplementary matrix.
private final Matrix displayMatrix = new Matrix();
// Temporary buffer used for getting the values out of a matrix.
private final float[] matrixValues = new float[9];
// The current bitmap being displayed.
protected final RotateBitmap bitmapDisplayed = new RotateBitmap(null, 0);
int thisWidth = -1;
int thisHeight = -1;
float maxZoom;
private Runnable onLayoutRunnable;
protected Handler handler = new Handler();
// ImageViewTouchBase will pass a Bitmap to the Recycler if it has finished
// its use of that Bitmap
public interface Recycler {
public void recycle(Bitmap b);
}
private Recycler recycler;
public ImageViewTouchBase(Context context) {
super(context);
init();
}
public ImageViewTouchBase(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ImageViewTouchBase(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public void setRecycler(Recycler recycler) {
this.recycler = recycler;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
thisWidth = right - left;
thisHeight = bottom - top;
Runnable r = onLayoutRunnable;
if (r != null) {
onLayoutRunnable = null;
r.run();
}
if (bitmapDisplayed.getBitmap() != null) {
getProperBaseMatrix(bitmapDisplayed, baseMatrix, true);
setImageMatrix(getImageViewMatrix());
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
event.startTracking();
return true;
}
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() && !event.isCanceled()) {
if (getScale() > 1.0f) {
// If we're zoomed in, pressing Back jumps out to show the
// entire image, otherwise Back returns the user to the gallery
zoomTo(1.0f);
return true;
}
}
return super.onKeyUp(keyCode, event);
}
@Override
public void setImageBitmap(Bitmap bitmap) {
setImageBitmap(bitmap, 0);
}
private void setImageBitmap(Bitmap bitmap, int rotation) {
super.setImageBitmap(bitmap);
Drawable d = getDrawable();
if (d != null) {
d.setDither(true);
}
Bitmap old = bitmapDisplayed.getBitmap();
bitmapDisplayed.setBitmap(bitmap);
bitmapDisplayed.setRotation(rotation);
if (old != null && old != bitmap && recycler != null) {
recycler.recycle(old);
}
}
public void clear() {
setImageBitmapResetBase(null, true);
}
// This function changes bitmap, reset base matrix according to the size
// of the bitmap, and optionally reset the supplementary matrix
public void setImageBitmapResetBase(final Bitmap bitmap, final boolean resetSupp) {
setImageRotateBitmapResetBase(new RotateBitmap(bitmap, 0), resetSupp);
}
public void setImageRotateBitmapResetBase(final RotateBitmap bitmap, final boolean resetSupp) {
final int viewWidth = getWidth();
if (viewWidth <= 0) {
onLayoutRunnable = new Runnable() {
public void run() {
setImageRotateBitmapResetBase(bitmap, resetSupp);
}
};
return;
}
if (bitmap.getBitmap() != null) {
getProperBaseMatrix(bitmap, baseMatrix, true);
setImageBitmap(bitmap.getBitmap(), bitmap.getRotation());
} else {
baseMatrix.reset();
setImageBitmap(null);
}
if (resetSupp) {
suppMatrix.reset();
}
setImageMatrix(getImageViewMatrix());
maxZoom = calculateMaxZoom();
}
// Center as much as possible in one or both axis. Centering is defined as follows:
// * If the image is scaled down below the view's dimensions then center it.
// * If the image is scaled larger than the view and is translated out of view then translate it back into view.
protected void center() {
final Bitmap bitmap = bitmapDisplayed.getBitmap();
if (bitmap == null) {
return;
}
Matrix m = getImageViewMatrix();
RectF rect = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight());
m.mapRect(rect);
float height = rect.height();
float width = rect.width();
float deltaX = 0, deltaY = 0;
deltaY = centerVertical(rect, height, deltaY);
deltaX = centerHorizontal(rect, width, deltaX);
postTranslate(deltaX, deltaY);
setImageMatrix(getImageViewMatrix());
}
private float centerVertical(RectF rect, float height, float deltaY) {
int viewHeight = getHeight();
if (height < viewHeight) {
deltaY = (viewHeight - height) / 2 - rect.top;
} else if (rect.top > 0) {
deltaY = -rect.top;
} else if (rect.bottom < viewHeight) {
deltaY = getHeight() - rect.bottom;
}
return deltaY;
}
private float centerHorizontal(RectF rect, float width, float deltaX) {
int viewWidth = getWidth();
if (width < viewWidth) {
deltaX = (viewWidth - width) / 2 - rect.left;
} else if (rect.left > 0) {
deltaX = -rect.left;
} else if (rect.right < viewWidth) {
deltaX = viewWidth - rect.right;
}
return deltaX;
}
private void init() {
setScaleType(ImageView.ScaleType.MATRIX);
}
protected float getValue(Matrix matrix, int whichValue) {
matrix.getValues(matrixValues);
return matrixValues[whichValue];
}
// Get the scale factor out of the matrix.
protected float getScale(Matrix matrix) {
return getValue(matrix, Matrix.MSCALE_X);
}
protected float getScale() {
return getScale(suppMatrix);
}
// Setup the base matrix so that the image is centered and scaled properly.
private void getProperBaseMatrix(RotateBitmap bitmap, Matrix matrix, boolean includeRotation) {
float viewWidth = getWidth();
float viewHeight = getHeight();
float w = bitmap.getWidth();
float h = bitmap.getHeight();
matrix.reset();
// We limit up-scaling to 3x otherwise the result may look bad if it's a small icon
float widthScale = Math.min(viewWidth / w, 3.0f);
float heightScale = Math.min(viewHeight / h, 3.0f);
float scale = Math.min(widthScale, heightScale);
if (includeRotation) {
matrix.postConcat(bitmap.getRotateMatrix());
}
matrix.postScale(scale, scale);
matrix.postTranslate((viewWidth - w * scale) / 2F, (viewHeight - h * scale) / 2F);
}
// Combine the base matrix and the supp matrix to make the final matrix
protected Matrix getImageViewMatrix() {
// The final matrix is computed as the concatentation of the base matrix
// and the supplementary matrix
displayMatrix.set(baseMatrix);
displayMatrix.postConcat(suppMatrix);
return displayMatrix;
}
public Matrix getUnrotatedMatrix(){
Matrix unrotated = new Matrix();
getProperBaseMatrix(bitmapDisplayed, unrotated, false);
unrotated.postConcat(suppMatrix);
return unrotated;
}
protected float calculateMaxZoom() {
if (bitmapDisplayed.getBitmap() == null) {
return 1F;
}
float fw = (float) bitmapDisplayed.getWidth() / (float) thisWidth;
float fh = (float) bitmapDisplayed.getHeight() / (float) thisHeight;
return Math.max(fw, fh) * 4; // 400%
}
protected void zoomTo(float scale, float centerX, float centerY) {
if (scale > maxZoom) {
scale = maxZoom;
}
float oldScale = getScale();
float deltaScale = scale / oldScale;
suppMatrix.postScale(deltaScale, deltaScale, centerX, centerY);
setImageMatrix(getImageViewMatrix());
center();
}
protected void zoomTo(final float scale, final float centerX,
final float centerY, final float durationMs) {
final float incrementPerMs = (scale - getScale()) / durationMs;
final float oldScale = getScale();
final long startTime = System.currentTimeMillis();
handler.post(new Runnable() {
public void run() {
long now = System.currentTimeMillis();
float currentMs = Math.min(durationMs, now - startTime);
float target = oldScale + (incrementPerMs * currentMs);
zoomTo(target, centerX, centerY);
if (currentMs < durationMs) {
handler.post(this);
}
}
});
}
protected void zoomTo(float scale) {
float cx = getWidth() / 2F;
float cy = getHeight() / 2F;
zoomTo(scale, cx, cy);
}
protected void zoomIn() {
zoomIn(SCALE_RATE);
}
protected void zoomOut() {
zoomOut(SCALE_RATE);
}
protected void zoomIn(float rate) {
if (getScale() >= maxZoom) {
return; // Don't let the user zoom into the molecular level
}
if (bitmapDisplayed.getBitmap() == null) {
return;
}
float cx = getWidth() / 2F;
float cy = getHeight() / 2F;
suppMatrix.postScale(rate, rate, cx, cy);
setImageMatrix(getImageViewMatrix());
}
protected void zoomOut(float rate) {
if (bitmapDisplayed.getBitmap() == null) {
return;
}
float cx = getWidth() / 2F;
float cy = getHeight() / 2F;
// Zoom out to at most 1x
Matrix tmp = new Matrix(suppMatrix);
tmp.postScale(1F / rate, 1F / rate, cx, cy);
if (getScale(tmp) < 1F) {
suppMatrix.setScale(1F, 1F, cx, cy);
} else {
suppMatrix.postScale(1F / rate, 1F / rate, cx, cy);
}
setImageMatrix(getImageViewMatrix());
center();
}
protected void postTranslate(float dx, float dy) {
suppMatrix.postTranslate(dx, dy);
}
protected void panBy(float dx, float dy) {
postTranslate(dx, dy);
setImageMatrix(getImageViewMatrix());
}
}
================================================
FILE: lib/src/main/java/com/soundcloud/android/crop/Log.java
================================================
package com.soundcloud.android.crop;
class Log {
private static final String TAG = "android-crop";
public static void e(String msg) {
android.util.Log.e(TAG, msg);
}
public static void e(String msg, Throwable e) {
android.util.Log.e(TAG, msg, e);
}
}
================================================
FILE: lib/src/main/java/com/soundcloud/android/crop/MonitoredActivity.java
================================================
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.soundcloud.android.crop;
import android.app.Activity;
import android.os.Bundle;
import java.util.ArrayList;
/*
* Modified from original in AOSP.
*/
abstract class MonitoredActivity extends Activity {
private final ArrayList<LifeCycleListener> listeners = new ArrayList<LifeCycleListener>();
public static interface LifeCycleListener {
public void onActivityCreated(MonitoredActivity activity);
public void onActivityDestroyed(MonitoredActivity activity);
public void onActivityStarted(MonitoredActivity activity);
public void onActivityStopped(MonitoredActivity activity);
}
public static class LifeCycleAdapter implements LifeCycleListener {
public void onActivityCreated(MonitoredActivity activity) {}
public void onActivityDestroyed(MonitoredActivity activity) {}
public void onActivityStarted(MonitoredActivity activity) {}
public void onActivityStopped(MonitoredActivity activity) {}
}
public void addLifeCycleListener(LifeCycleListener listener) {
if (listeners.contains(listener)) return;
listeners.add(listener);
}
public void removeLifeCycleListener(LifeCycleListener listener) {
listeners.remove(listener);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
for (LifeCycleListener listener : listeners) {
listener.onActivityCreated(this);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
for (LifeCycleListener listener : listeners) {
listener.onActivityDestroyed(this);
}
}
@Override
protected void onStart() {
super.onStart();
for (LifeCycleListener listener : listeners) {
listener.onActivityStarted(this);
}
}
@Override
protected void onStop() {
super.onStop();
for (LifeCycleListener listener : listeners) {
listener.onActivityStopped(this);
}
}
}
================================================
FILE: lib/src/main/java/com/soundcloud/android/crop/RotateBitmap.java
================================================
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.soundcloud.android.crop;
import android.graphics.Bitmap;
import android.graphics.Matrix;
/*
* Modified from original in AOSP.
*/
class RotateBitmap {
private Bitmap bitmap;
private int rotation;
public RotateBitmap(Bitmap bitmap, int rotation) {
this.bitmap = bitmap;
this.rotation = rotation % 360;
}
public void setRotation(int rotation) {
this.rotation = rotation;
}
public int getRotation() {
return rotation;
}
public Bitmap getBitmap() {
return bitmap;
}
public void setBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
}
public Matrix getRotateMatrix() {
// By default this is an identity matrix
Matrix matrix = new Matrix();
if (bitmap != null && rotation != 0) {
// We want to do the rotation at origin, but since the bounding
// rectangle will be changed after rotation, so the delta values
// are based on old & new width/height respectively.
int cx = bitmap.getWidth() / 2;
int cy = bitmap.getHeight() / 2;
matrix.preTranslate(-cx, -cy);
matrix.postRotate(rotation);
matrix.postTranslate(getWidth() / 2, getHeight() / 2);
}
return matrix;
}
public boolean isOrientationChanged() {
return (rotation / 90) % 2 != 0;
}
public int getHeight() {
if (bitmap == null) return 0;
if (isOrientationChanged()) {
return bitmap.getWidth();
} else {
return bitmap.getHeight();
}
}
public int getWidth() {
if (bitmap == null) return 0;
if (isOrientationChanged()) {
return bitmap.getHeight();
} else {
return bitmap.getWidth();
}
}
public void recycle() {
if (bitmap != null) {
bitmap.recycle();
bitmap = null;
}
}
}
================================================
FILE: lib/src/main/res/drawable/crop__selectable_background.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:exitFadeDuration="@android:integer/config_mediumAnimTime">
<item android:state_pressed="true">
<shape>
<solid android:color="@color/crop__selector_pressed" />
</shape>
</item>
<item android:state_focused="true">
<shape>
<solid android:color="@color/crop__selector_focused" />
</shape>
</item>
<item android:drawable="@android:color/transparent" />
</selector>
================================================
FILE: lib/src/main/res/drawable/crop__texture.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/crop__tile"
android:tileMode="repeat" />
================================================
FILE: lib/src/main/res/drawable-v21/crop__selectable_background.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/crop__selector_pressed">
<item android:drawable="@color/crop__button_bar" />
</ripple>
================================================
FILE: lib/src/main/res/layout/crop__activity_crop.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="@+id/done_cancel_bar"
layout="@layout/crop__layout_done_cancel" />
<com.soundcloud.android.crop.CropImageView
android:id="@+id/crop_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/crop__texture"
android:layout_below="@id/done_cancel_bar" />
</RelativeLayout>
================================================
FILE: lib/src/main/res/layout/crop__layout_done_cancel.xml
================================================
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/Crop.DoneCancelBar">
<FrameLayout
android:id="@+id/btn_cancel"
style="@style/Crop.ActionButton">
<TextView style="@style/Crop.ActionButtonText.Cancel" />
</FrameLayout>
<FrameLayout
android:id="@+id/btn_done"
style="@style/Crop.ActionButton">
<TextView style="@style/Crop.ActionButtonText.Done" />
</FrameLayout>
</LinearLayout>
================================================
FILE: lib/src/main/res/values/attrs.xml
================================================
<resources>
<attr name="cropImageStyle" format="reference" />
<declare-styleable name="CropImageView">
<attr name="highlightColor" format="reference|color" />
<attr name="showThirds" format="boolean" />
<attr name="showCircle" format="boolean" />
<attr name="showHandles">
<enum name="changing" value="0" />
<enum name="always" value="1" />
<enum name="never" value="2" />
</attr>
</declare-styleable>
</resources>
================================================
FILE: lib/src/main/res/values/colors.xml
================================================
<resources>
<color name="crop__button_bar">#f3f3f3</color>
<color name="crop__button_text">#666666</color>
<color name="crop__selector_pressed">#1a000000</color>
<color name="crop__selector_focused">#77000000</color>
</resources>
================================================
FILE: lib/src/main/res/values/dimens.xml
================================================
<resources>
<dimen name="crop__bar_height">56dp</dimen>
</resources>
================================================
FILE: lib/src/main/res/values/strings.xml
================================================
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="crop__saving">Saving picture…</string>
<string name="crop__wait">Please wait…</string>
<string name="crop__pick_error">No image sources available</string>
<string name="crop__done">DONE</string>
<string name="crop__cancel" tools:ignore="ButtonCase">CANCEL</string>
</resources>
================================================
FILE: lib/src/main/res/values/styles.xml
================================================
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="Crop"></style>
<style name="Crop.DoneCancelBar">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">@dimen/crop__bar_height</item>
<item name="android:orientation">horizontal</item>
<item name="android:divider">@drawable/crop__divider</item>
<item name="android:showDividers" tools:ignore="NewApi">middle</item>
<item name="android:dividerPadding" tools:ignore="NewApi">12dp</item>
<item name="android:background">@color/crop__button_bar</item>
</style>
<style name="Crop.ActionButton">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">match_parent</item>
<item name="android:layout_weight">1</item>
<item name="android:background">@drawable/crop__selectable_background</item>
</style>
<style name="Crop.ActionButtonText">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_gravity">center</item>
<item name="android:gravity">center_vertical</item>
<item name="android:paddingRight">20dp</item> <!-- Offsets left drawable -->
<item name="android:drawablePadding">8dp</item>
<item name="android:textColor">@color/crop__button_text</item>
<item name="android:textStyle">bold</item>
<item name="android:textSize">13sp</item>
</style>
<style name="Crop.ActionButtonText.Done">
<item name="android:drawableLeft">@drawable/crop__ic_done</item>
<item name="android:text">@string/crop__done</item>
</style>
<style name="Crop.ActionButtonText.Cancel">
<item name="android:drawableLeft">@drawable/crop__ic_cancel</item>
<item name="android:text">@string/crop__cancel</item>
</style>
</resources>
================================================
FILE: lib/src/main/res/values-ar/strings.xml
================================================
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="crop__saving">جارى حفظ الصورة …</string>
<string name="crop__wait">رجاء الأنتظار …</string>
<string name="crop__pick_error">الصورة غير متاحة</string>
<string name="crop__done">تم</string>
<string name="crop__cancel" tools:ignore="ButtonCase">الغاء</string>
</resources>
================================================
FILE: lib/src/main/res/values-ca/strings.xml
================================================
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="crop__saving">Guardant imatge…</string>
<string name="crop__wait">Si us plau esperi…</string>
<string name="crop__pick_error">No hi ha imatges disponibles</string>
<string name="crop__done">ACCEPTAR</string>
<string name="crop__cancel" tools:ignore="ButtonCase">CANCEL·LAR</string>
</resources>
================================================
FILE: lib/src/main/res/values-de/strings.xml
================================================
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="crop__saving">Bild speichern…</string>
<string name="crop__wait">Bitte warten…</string>
<string name="crop__pick_error">Keine Bildquellen verfügbar</string>
<string name="crop__done">übernehmen</string>
<string name="crop__cancel" tools:ignore="ButtonCase">abbrechen</string>
</resources>
================================================
FILE: lib/src/main/res/values-es/strings.xml
================================================
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="crop__saving">Guardando imagen…</string>
<string name="crop__wait">Por favor espere…</string>
<string name="crop__pick_error">No hay imágenes disponibles</string>
<string name="crop__done">ACEPTAR</string>
<string name="crop__cancel" tools:ignore="ButtonCase">CANCELAR</string>
</resources>
================================================
FILE: lib/src/main/res/values-fa/strings.xml
================================================
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="crop__saving">در حال ذخیره سازی</string>
<string name="crop__wait">لطفاً صبر کنید ...</string>
<string name="crop__pick_error">تصویری در دسترس نیست</string>
<string name="crop__done">تأیید</string>
<string name="crop__cancel" tools:ignore="ButtonCase">انصراف</string>
</resources>
================================================
FILE: lib/src/main/res/values-fr/strings.xml
================================================
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="crop__saving">Enregistrement de l\'image…</string>
<string name="crop__wait">Veuillez patienter…</string>
<string name="crop__pick_error">Aucune image disponible</string>
<string name="crop__done">ACCEPTER</string>
<string name="crop__cancel" tools:ignore="ButtonCase">ANNULER</string>
</resources>
================================================
FILE: lib/src/main/res/values-in/strings.xml
================================================
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="crop__saving">Menyimpan gambar…</string>
<string name="crop__wait">Silakan tunggu…</string>
<string name="crop__pick_error">Tidak ada sumber gambar yang tersedia</string>
<string name="crop__done">SELESAI</string>
<string name="crop__cancel" tools:ignore="ButtonCase">BATAL</string>
</resources>
================================================
FILE: lib/src/main/res/values-it/strings.xml
================================================
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="crop__saving">Salvataggio immagine…</string>
<string name="crop__wait">Attendere prego…</string>
<string name="crop__pick_error">Nessuna immagine disponibile</string>
<string name="crop__done">ACCETTA</string>
<string name="crop__cancel" tools:ignore="ButtonCase">ANNULLA</string>
</resources>
================================================
FILE: lib/src/main/res/values-ja/strings.xml
================================================
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="crop__saving">保存中…</string>
<string name="crop__wait">お待ちください…</string>
<string name="crop__pick_error">画像が見つかりません</string>
<string name="crop__done">決定</string>
<string name="crop__cancel" tools:ignore="ButtonCase">キャンセル</string>
</resources>
================================================
FILE: lib/src/main/res/values-ko/strings.xml
================================================
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="crop__saving">사진을 저장중입니다…</string>
<string name="crop__wait">잠시만 기다려주세요…</string>
<string name="crop__pick_error">이미지가 존재하지 않습니다.</string>
<string name="crop__done">확인</string>
<string name="crop__cancel" tools:ignore="ButtonCase">취소</string>
</resources>
================================================
FILE: lib/src/main/res/values-land/dimens.xml
================================================
<resources>
<dimen name="crop__bar_height">48dp</dimen>
</resources>
================================================
FILE: lib/src/main/res/values-large/dimens.xml
================================================
<resources>
<dimen name="crop__bar_height">64dp</dimen>
</resources>
================================================
FILE: lib/src/main/res/values-pt/strings.xml
================================================
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="crop__saving">Salvando imagem…</string>
<string name="crop__wait">Por favor, aguarde…</string>
<string name="crop__pick_error">Sem fontes de imagem disponíveis</string>
<string name="crop__done">FINALIZADO</string>
<string name="crop__cancel" tools:ignore="ButtonCase">CANCELAR</string>
</resources>
================================================
FILE: lib/src/main/res/values-ru/strings.xml
================================================
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="crop__saving">Изображение сохраняется…</string>
<string name="crop__wait">Пожалуйста, подождите…</string>
<string name="crop__pick_error">Нет доступных изображений</string>
<string name="crop__done">ГОТОВО</string>
<string name="crop__cancel" tools:ignore="ButtonCase">ОТМЕНА</string>
</resources>
================================================
FILE: lib/src/main/res/values-sv/strings.xml
================================================
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="crop__saving">Sparar bild…</string>
<string name="crop__wait">Var god vänta…</string>
<string name="crop__pick_error">Inga bildkällor tillgängliga</string>
<string name="crop__done">KLAR</string>
<string name="crop__cancel" tools:ignore="ButtonCase">AVBRYT</string>
</resources>
================================================
FILE: lib/src/main/res/values-tr/strings.xml
================================================
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="crop__saving">Fotoğraf kaydediliyor…</string>
<string name="crop__wait">Lütfen bekleyin…</string>
<string name="crop__pick_error">Fotoğraf bulunamadı</string>
<string name="crop__done">TAMAM</string>
<string name="crop__cancel" tools:ignore="ButtonCase">ÇIKIŞ</string>
</resources>
================================================
FILE: lib/src/main/res/values-v21/colors.xml
================================================
<resources>
<color name="crop__selector_pressed">#aaaaaa</color>
</resources>
================================================
FILE: lib/src/main/res/values-zh-rCN/strings.xml
================================================
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="crop__saving">正在保存照片…</string>
<string name="crop__wait">请等待…</string>
<string name="crop__pick_error">无效的图片</string>
<string name="crop__done">完成</string>
<string name="crop__cancel" tools:ignore="ButtonCase">取消</string>
</resources>
================================================
FILE: lib/src/main/res/values-zh-rTW/strings.xml
================================================
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="crop__saving">正在儲存相片…</string>
<string name="crop__wait">請稍候…</string>
<string name="crop__pick_error">沒有可用的圖片來源</string>
<string name="crop__done">完成</string>
<string name="crop__cancel" tools:ignore="ButtonCase">取消</string>
</resources>
================================================
FILE: settings.gradle
================================================
include ':lib', ':example'
gitextract_a6ryjm71/ ├── .gitignore ├── .publishing/ │ └── sonatype.gradle ├── .travis.yml ├── CHANGELOG.md ├── README.md ├── build.gradle ├── example/ │ ├── build.gradle │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── com/ │ │ └── soundcloud/ │ │ └── android/ │ │ └── crop/ │ │ └── example/ │ │ └── MainActivity.java │ └── res/ │ ├── drawable/ │ │ └── texture.xml │ ├── layout/ │ │ └── activity_main.xml │ ├── menu/ │ │ └── activity_main.xml │ └── values/ │ ├── colors.xml │ ├── strings.xml │ └── theme.xml ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── lib/ │ ├── build.gradle │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── soundcloud/ │ │ └── android/ │ │ └── crop/ │ │ ├── BaseTestCase.java │ │ └── CropBuilderTest.java │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── com/ │ │ └── soundcloud/ │ │ └── android/ │ │ └── crop/ │ │ ├── Crop.java │ │ ├── CropImageActivity.java │ │ ├── CropImageView.java │ │ ├── CropUtil.java │ │ ├── HighlightView.java │ │ ├── ImageViewTouchBase.java │ │ ├── Log.java │ │ ├── MonitoredActivity.java │ │ └── RotateBitmap.java │ └── res/ │ ├── drawable/ │ │ ├── crop__selectable_background.xml │ │ └── crop__texture.xml │ ├── drawable-v21/ │ │ └── crop__selectable_background.xml │ ├── layout/ │ │ ├── crop__activity_crop.xml │ │ └── crop__layout_done_cancel.xml │ ├── values/ │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ ├── values-ar/ │ │ └── strings.xml │ ├── values-ca/ │ │ └── strings.xml │ ├── values-de/ │ │ └── strings.xml │ ├── values-es/ │ │ └── strings.xml │ ├── values-fa/ │ │ └── strings.xml │ ├── values-fr/ │ │ └── strings.xml │ ├── values-in/ │ │ └── strings.xml │ ├── values-it/ │ │ └── strings.xml │ ├── values-ja/ │ │ └── strings.xml │ ├── values-ko/ │ │ └── strings.xml │ ├── values-land/ │ │ └── dimens.xml │ ├── values-large/ │ │ └── dimens.xml │ ├── values-pt/ │ │ └── strings.xml │ ├── values-ru/ │ │ └── strings.xml │ ├── values-sv/ │ │ └── strings.xml │ ├── values-tr/ │ │ └── strings.xml │ ├── values-v21/ │ │ └── colors.xml │ ├── values-zh-rCN/ │ │ └── strings.xml │ └── values-zh-rTW/ │ └── strings.xml └── settings.gradle
SYMBOL INDEX (183 symbols across 12 files)
FILE: example/src/main/java/com/soundcloud/android/crop/example/MainActivity.java
class MainActivity (line 16) | public class MainActivity extends Activity {
method onCreate (line 20) | @Override
method onCreateOptionsMenu (line 27) | @Override
method onOptionsItemSelected (line 33) | @Override
method onActivityResult (line 43) | @Override
method beginCrop (line 52) | private void beginCrop(Uri source) {
method handleCrop (line 57) | private void handleCrop(int resultCode, Intent result) {
FILE: lib/src/androidTest/java/com/soundcloud/android/crop/BaseTestCase.java
class BaseTestCase (line 5) | public class BaseTestCase extends InstrumentationTestCase {
method setUp (line 7) | @Override
FILE: lib/src/androidTest/java/com/soundcloud/android/crop/CropBuilderTest.java
class CropBuilderTest (line 14) | public class CropBuilderTest extends BaseTestCase {
method setUp (line 19) | @Override
method testInputUriSetAsData (line 28) | public void testInputUriSetAsData() {
method testOutputUriSetAsExtra (line 32) | public void testOutputUriSetAsExtra() {
method testAspectRatioSetAsExtras (line 39) | public void testAspectRatioSetAsExtras() {
method testFixedAspectRatioSetAsExtras (line 48) | public void testFixedAspectRatioSetAsExtras() {
method testMaxSizeSetAsExtras (line 57) | public void testMaxSizeSetAsExtras() {
method testBuildsIntentWithMultipleOptions (line 66) | public void testBuildsIntentWithMultipleOptions() {
method testAsPngSetAsExtras (line 77) | public void testAsPngSetAsExtras() {
FILE: lib/src/main/java/com/soundcloud/android/crop/Crop.java
class Crop (line 17) | public class Crop {
type Extra (line 23) | interface Extra {
method of (line 40) | public static Crop of(Uri source, Uri destination) {
method Crop (line 44) | private Crop(Uri source, Uri destination) {
method withAspect (line 56) | public Crop withAspect(int x, int y) {
method asSquare (line 65) | public Crop asSquare() {
method withMaxSize (line 77) | public Crop withMaxSize(int width, int height) {
method asPng (line 87) | public Crop asPng(boolean asPng) {
method start (line 97) | public void start(Activity activity) {
method start (line 107) | public void start(Activity activity, int requestCode) {
method start (line 117) | public void start(Context context, Fragment fragment) {
method start (line 127) | public void start(Context context, android.support.v4.app.Fragment fra...
method start (line 138) | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
method start (line 150) | public void start(Context context, android.support.v4.app.Fragment fra...
method getIntent (line 160) | public Intent getIntent(Context context) {
method getOutput (line 170) | public static Uri getOutput(Intent result) {
method getError (line 180) | public static Throwable getError(Intent result) {
method pickImage (line 189) | public static void pickImage(Activity activity) {
method pickImage (line 199) | public static void pickImage(Context context, Fragment fragment) {
method pickImage (line 209) | public static void pickImage(Context context, android.support.v4.app.F...
method pickImage (line 219) | public static void pickImage(Activity activity, int requestCode) {
method pickImage (line 234) | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
method pickImage (line 250) | public static void pickImage(Context context, android.support.v4.app.F...
method getImagePicker (line 258) | private static Intent getImagePicker() {
method showImagePickerError (line 262) | private static void showImagePickerError(Context context) {
FILE: lib/src/main/java/com/soundcloud/android/crop/CropImageActivity.java
class CropImageActivity (line 45) | public class CropImageActivity extends MonitoredActivity {
method onCreate (line 71) | @Override
method setupWindowFlags (line 85) | @TargetApi(Build.VERSION_CODES.KITKAT)
method setupViews (line 93) | private void setupViews() {
method loadInput (line 120) | private void loadInput() {
method calculateBitmapSampleSize (line 156) | private int calculateBitmapSampleSize(Uri bitmapUri) throws IOException {
method getMaxImageSize (line 175) | private int getMaxImageSize() {
method getMaxTextureSize (line 184) | private int getMaxTextureSize() {
method startCrop (line 191) | private void startCrop() {
class Cropper (line 219) | private class Cropper {
method makeDefault (line 221) | private void makeDefault() {
method crop (line 253) | public void crop() {
method onSaveClicked (line 267) | private void onSaveClicked() {
method saveImage (line 307) | private void saveImage(Bitmap croppedImage) {
method decodeRegionCrop (line 322) | private Bitmap decodeRegionCrop(Rect rect, int outWidth, int outHeight) {
method clearImageView (line 372) | private void clearImageView() {
method saveOutput (line 380) | private void saveOutput(Bitmap croppedImage) {
method onDestroy (line 416) | @Override
method onSearchRequested (line 424) | @Override
method isSaving (line 429) | public boolean isSaving() {
method setResultUri (line 433) | private void setResultUri(Uri uri) {
method setResultException (line 437) | private void setResultException(Throwable throwable) {
FILE: lib/src/main/java/com/soundcloud/android/crop/CropImageView.java
class CropImageView (line 12) | public class CropImageView extends ImageViewTouchBase {
method CropImageView (line 23) | public CropImageView(Context context) {
method CropImageView (line 27) | public CropImageView(Context context, AttributeSet attrs) {
method CropImageView (line 31) | public CropImageView(Context context, AttributeSet attrs, int defStyle) {
method onLayout (line 35) | @Override
method zoomTo (line 50) | @Override
method zoomIn (line 59) | @Override
method zoomOut (line 68) | @Override
method postTranslate (line 77) | @Override
method onTouchEvent (line 86) | @Override
method ensureVisible (line 139) | private void ensureVisible(HighlightView hv) {
method centerBasedOnHighlightView (line 158) | private void centerBasedOnHighlightView(HighlightView hv) {
method onDraw (line 183) | @Override
method add (line 191) | public void add(HighlightView hv) {
FILE: lib/src/main/java/com/soundcloud/android/crop/CropUtil.java
class CropUtil (line 41) | class CropUtil {
method closeSilently (line 46) | public static void closeSilently(@Nullable Closeable c) {
method getExifRotation (line 55) | public static int getExifRotation(File imageFile) {
method copyExifRotation (line 76) | public static boolean copyExifRotation(File sourceFile, File destFile) {
method getFromMediaUri (line 90) | @Nullable
method getTempFilename (line 125) | private static String getTempFilename(Context context) throws IOExcept...
method getFromMediaUriPfd (line 131) | @Nullable
method startBackgroundJob (line 160) | public static void startBackgroundJob(MonitoredActivity activity,
class BackgroundJob (line 169) | private static class BackgroundJob extends MonitoredActivity.LifeCycle...
method run (line 176) | public void run() {
method BackgroundJob (line 182) | public BackgroundJob(MonitoredActivity activity, Runnable job,
method run (line 191) | public void run() {
method onActivityDestroyed (line 199) | @Override
method onActivityStopped (line 207) | @Override
method onActivityStarted (line 212) | @Override
FILE: lib/src/main/java/com/soundcloud/android/crop/HighlightView.java
class HighlightView (line 42) | class HighlightView {
type ModifyMode (line 55) | enum ModifyMode { None, Move, Grow }
type HandleMode (line 56) | enum HandleMode { Changing, Always, Never }
method HighlightView (line 80) | public HighlightView(View context) {
method initStyles (line 85) | private void initStyles(Context context) {
method setup (line 100) | public void setup(Matrix m, Rect imageRect, RectF cropRect, boolean ma...
method dpToPx (line 123) | private float dpToPx(float dp) {
method draw (line 127) | protected void draw(Canvas canvas) {
method drawOutsideFallback (line 169) | private void drawOutsideFallback(Canvas canvas) {
method isClipPathSupported (line 181) | @SuppressLint("NewApi")
method drawHandles (line 193) | private void drawHandles(Canvas canvas) {
method drawThirds (line 203) | private void drawThirds(Canvas canvas) {
method drawCircle (line 218) | private void drawCircle(Canvas canvas) {
method setMode (line 223) | public void setMode(ModifyMode mode) {
method getHit (line 231) | public int getHit(float x, float y) {
method handleMotion (line 266) | void handleMotion(int edge, float dx, float dy) {
method moveBy (line 290) | void moveBy(float dx, float dy) {
method growBy (line 311) | void growBy(float dx, float dy) {
method getScaledCropRect (line 369) | public Rect getScaledCropRect(float scale) {
method computeLayout (line 375) | private Rect computeLayout() {
method invalidate (line 383) | public void invalidate() {
method hasFocus (line 387) | public boolean hasFocus() {
method setFocus (line 391) | public void setFocus(boolean isFocused) {
FILE: lib/src/main/java/com/soundcloud/android/crop/ImageViewTouchBase.java
class ImageViewTouchBase (line 32) | abstract class ImageViewTouchBase extends ImageView {
type Recycler (line 73) | public interface Recycler {
method recycle (line 74) | public void recycle(Bitmap b);
method ImageViewTouchBase (line 79) | public ImageViewTouchBase(Context context) {
method ImageViewTouchBase (line 84) | public ImageViewTouchBase(Context context, AttributeSet attrs) {
method ImageViewTouchBase (line 89) | public ImageViewTouchBase(Context context, AttributeSet attrs, int def...
method setRecycler (line 94) | public void setRecycler(Recycler recycler) {
method onLayout (line 98) | @Override
method onKeyDown (line 114) | @Override
method onKeyUp (line 123) | @Override
method setImageBitmap (line 136) | @Override
method setImageBitmap (line 141) | private void setImageBitmap(Bitmap bitmap, int rotation) {
method clear (line 157) | public void clear() {
method setImageBitmapResetBase (line 164) | public void setImageBitmapResetBase(final Bitmap bitmap, final boolean...
method setImageRotateBitmapResetBase (line 168) | public void setImageRotateBitmapResetBase(final RotateBitmap bitmap, f...
method center (line 198) | protected void center() {
method centerVertical (line 220) | private float centerVertical(RectF rect, float height, float deltaY) {
method centerHorizontal (line 232) | private float centerHorizontal(RectF rect, float width, float deltaX) {
method init (line 244) | private void init() {
method getValue (line 248) | protected float getValue(Matrix matrix, int whichValue) {
method getScale (line 254) | protected float getScale(Matrix matrix) {
method getScale (line 258) | protected float getScale() {
method getProperBaseMatrix (line 263) | private void getProperBaseMatrix(RotateBitmap bitmap, Matrix matrix, b...
method getImageViewMatrix (line 284) | protected Matrix getImageViewMatrix() {
method getUnrotatedMatrix (line 292) | public Matrix getUnrotatedMatrix(){
method calculateMaxZoom (line 299) | protected float calculateMaxZoom() {
method zoomTo (line 309) | protected void zoomTo(float scale, float centerX, float centerY) {
method zoomTo (line 322) | protected void zoomTo(final float scale, final float centerX,
method zoomTo (line 342) | protected void zoomTo(float scale) {
method zoomIn (line 348) | protected void zoomIn() {
method zoomOut (line 352) | protected void zoomOut() {
method zoomIn (line 356) | protected void zoomIn(float rate) {
method zoomOut (line 371) | protected void zoomOut(float rate) {
method postTranslate (line 392) | protected void postTranslate(float dx, float dy) {
method panBy (line 396) | protected void panBy(float dx, float dy) {
FILE: lib/src/main/java/com/soundcloud/android/crop/Log.java
class Log (line 3) | class Log {
method e (line 7) | public static void e(String msg) {
method e (line 11) | public static void e(String msg, Throwable e) {
FILE: lib/src/main/java/com/soundcloud/android/crop/MonitoredActivity.java
class MonitoredActivity (line 27) | abstract class MonitoredActivity extends Activity {
type LifeCycleListener (line 31) | public static interface LifeCycleListener {
method onActivityCreated (line 32) | public void onActivityCreated(MonitoredActivity activity);
method onActivityDestroyed (line 33) | public void onActivityDestroyed(MonitoredActivity activity);
method onActivityStarted (line 34) | public void onActivityStarted(MonitoredActivity activity);
method onActivityStopped (line 35) | public void onActivityStopped(MonitoredActivity activity);
class LifeCycleAdapter (line 38) | public static class LifeCycleAdapter implements LifeCycleListener {
method onActivityCreated (line 39) | public void onActivityCreated(MonitoredActivity activity) {}
method onActivityDestroyed (line 40) | public void onActivityDestroyed(MonitoredActivity activity) {}
method onActivityStarted (line 41) | public void onActivityStarted(MonitoredActivity activity) {}
method onActivityStopped (line 42) | public void onActivityStopped(MonitoredActivity activity) {}
method addLifeCycleListener (line 45) | public void addLifeCycleListener(LifeCycleListener listener) {
method removeLifeCycleListener (line 50) | public void removeLifeCycleListener(LifeCycleListener listener) {
method onCreate (line 54) | @Override
method onDestroy (line 62) | @Override
method onStart (line 70) | @Override
method onStop (line 78) | @Override
FILE: lib/src/main/java/com/soundcloud/android/crop/RotateBitmap.java
class RotateBitmap (line 25) | class RotateBitmap {
method RotateBitmap (line 30) | public RotateBitmap(Bitmap bitmap, int rotation) {
method setRotation (line 35) | public void setRotation(int rotation) {
method getRotation (line 39) | public int getRotation() {
method getBitmap (line 43) | public Bitmap getBitmap() {
method setBitmap (line 47) | public void setBitmap(Bitmap bitmap) {
method getRotateMatrix (line 51) | public Matrix getRotateMatrix() {
method isOrientationChanged (line 67) | public boolean isOrientationChanged() {
method getHeight (line 71) | public int getHeight() {
method getWidth (line 80) | public int getWidth() {
method recycle (line 89) | public void recycle() {
Condensed preview — 63 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (113K chars).
[
{
"path": ".gitignore",
"chars": 56,
"preview": "\n/local.properties\n/.idea\nbuild/\n.gradle\n.DS_Store\n*.iml"
},
{
"path": ".publishing/sonatype.gradle",
"chars": 1810,
"preview": "configurations {\n archives {\n extendsFrom configurations.default\n }\n}\n\nsigning {\n required { has(\"releas"
},
{
"path": ".travis.yml",
"chars": 193,
"preview": "language: android\nsudo: false\n\nandroid:\n components:\n - build-tools-23.0.1\n - android-23\n - extra-android-supp"
},
{
"path": "CHANGELOG.md",
"chars": 1020,
"preview": "## Next\n* Fix max size crash when input cannot be decoded\n* Translations: German, Chinese (simplified & traditional)\n\n##"
},
{
"path": "README.md",
"chars": 2572,
"preview": "> I guess people are just cropping out all the sadness\n\nAn Android library project that provides a simple image cropping"
},
{
"path": "build.gradle",
"chars": 279,
"preview": "buildscript {\n repositories {\n mavenCentral()\n }\n dependencies {\n classpath 'com.android.tools.bu"
},
{
"path": "example/build.gradle",
"chars": 373,
"preview": "apply plugin: 'com.android.application'\n\narchivesBaseName = 'android-crop-example'\n\nandroid {\n compileSdkVersion 23\n "
},
{
"path": "example/src/main/AndroidManifest.xml",
"chars": 860,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n package="
},
{
"path": "example/src/main/java/com/soundcloud/android/crop/example/MainActivity.java",
"chars": 1986,
"preview": "package com.soundcloud.android.crop.example;\n\nimport com.soundcloud.android.crop.Crop;\n\nimport android.app.Activity;\nimp"
},
{
"path": "example/src/main/res/drawable/texture.xml",
"chars": 172,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<bitmap xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:s"
},
{
"path": "example/src/main/res/layout/activity_main.xml",
"chars": 525,
"preview": "<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:tools=\"http://schemas.android.com/tool"
},
{
"path": "example/src/main/res/menu/activity_main.xml",
"chars": 244,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n <item and"
},
{
"path": "example/src/main/res/values/colors.xml",
"chars": 70,
"preview": "<resources>\n\n <color name=\"highlight\">#f3f3f3</color>\n\n</resources>"
},
{
"path": "example/src/main/res/values/strings.xml",
"chars": 121,
"preview": "<resources>\n\n <string name=\"app_name\">Crop Demo</string>\n <string name=\"action_select\">Pick</string>\n\n</resources>"
},
{
"path": "example/src/main/res/values/theme.xml",
"chars": 521,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n <style tools:ignore=\"NewApi\" name=\"CustomTheme\" parent=\""
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 231,
"preview": "#Fri May 27 11:24:52 EDT 2016\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_"
},
{
"path": "gradle.properties",
"chars": 141,
"preview": "VERSION=1.0.1\nVERSION_CODE=1\n\nsigning.keyId=63A46540\nsigning.secretKeyRingFile=\nsigning.password=\n\nsonatypeUsername=jdam"
},
{
"path": "gradlew",
"chars": 5080,
"preview": "#!/usr/bin/env bash\n\n##############################################################################\n##\n## Gradle start "
},
{
"path": "gradlew.bat",
"chars": 2404,
"preview": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@r"
},
{
"path": "lib/build.gradle",
"chars": 882,
"preview": "apply plugin: 'com.android.library'\napply plugin: 'maven'\napply plugin: 'signing'\n//apply from: '../.publishing/sonatype"
},
{
"path": "lib/src/androidTest/java/com/soundcloud/android/crop/BaseTestCase.java",
"chars": 436,
"preview": "package com.soundcloud.android.crop;\n\nimport android.test.InstrumentationTestCase;\n\npublic class BaseTestCase extends In"
},
{
"path": "lib/src/androidTest/java/com/soundcloud/android/crop/CropBuilderTest.java",
"chars": 2588,
"preview": "package com.soundcloud.android.crop;\n\nimport static org.fest.assertions.api.Assertions.assertThat;\nimport static org.moc"
},
{
"path": "lib/src/main/AndroidManifest.xml",
"chars": 51,
"preview": "<manifest package=\"com.soundcloud.android.crop\" />\n"
},
{
"path": "lib/src/main/java/com/soundcloud/android/crop/Crop.java",
"chars": 7838,
"preview": "package com.soundcloud.android.crop;\n\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.a"
},
{
"path": "lib/src/main/java/com/soundcloud/android/crop/CropImageActivity.java",
"chars": 14880,
"preview": "/*\n * Copyright (C) 2007 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "lib/src/main/java/com/soundcloud/android/crop/CropImageView.java",
"chars": 6343,
"preview": "package com.soundcloud.android.crop;\n\nimport android.content.Context;\nimport android.graphics.Canvas;\nimport android.gra"
},
{
"path": "lib/src/main/java/com/soundcloud/android/crop/CropUtil.java",
"chars": 8056,
"preview": "/*\n * Copyright (C) 2009 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "lib/src/main/java/com/soundcloud/android/crop/HighlightView.java",
"chars": 14518,
"preview": "/*\n * Copyright (C) 2007 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "lib/src/main/java/com/soundcloud/android/crop/ImageViewTouchBase.java",
"chars": 12760,
"preview": "/*\n * Copyright (C) 2009 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "lib/src/main/java/com/soundcloud/android/crop/Log.java",
"chars": 292,
"preview": "package com.soundcloud.android.crop;\n\nclass Log {\n\n private static final String TAG = \"android-crop\";\n\n public sta"
},
{
"path": "lib/src/main/java/com/soundcloud/android/crop/MonitoredActivity.java",
"chars": 2690,
"preview": "/*\n * Copyright (C) 2009 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "lib/src/main/java/com/soundcloud/android/crop/RotateBitmap.java",
"chars": 2591,
"preview": "/*\n * Copyright (C) 2009 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "lib/src/main/res/drawable/crop__selectable_background.xml",
"chars": 560,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android"
},
{
"path": "lib/src/main/res/drawable/crop__texture.xml",
"chars": 178,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<bitmap xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:s"
},
{
"path": "lib/src/main/res/drawable-v21/crop__selectable_background.xml",
"chars": 226,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<ripple xmlns:android=\"http://schemas.android.com/apk/res/android\"\n andro"
},
{
"path": "lib/src/main/res/layout/crop__activity_crop.xml",
"chars": 609,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<RelativeLayout\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n "
},
{
"path": "lib/src/main/res/layout/crop__layout_done_cancel.xml",
"chars": 487,
"preview": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n style=\"@style/Crop.DoneCancelBar\">\n\n <Fr"
},
{
"path": "lib/src/main/res/values/attrs.xml",
"chars": 499,
"preview": "<resources>\n\n <attr name=\"cropImageStyle\" format=\"reference\" />\n\n <declare-styleable name=\"CropImageView\">\n "
},
{
"path": "lib/src/main/res/values/colors.xml",
"chars": 247,
"preview": "<resources>\n\n <color name=\"crop__button_bar\">#f3f3f3</color>\n <color name=\"crop__button_text\">#666666</color>\n "
},
{
"path": "lib/src/main/res/values/dimens.xml",
"chars": 74,
"preview": "<resources>\n\n <dimen name=\"crop__bar_height\">56dp</dimen>\n\n</resources>"
},
{
"path": "lib/src/main/res/values/strings.xml",
"chars": 374,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n <string name=\"crop__saving\">Saving picture…</string>\n "
},
{
"path": "lib/src/main/res/values/styles.xml",
"chars": 1954,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n <style name=\"Crop\"></style>\n\n <style name=\"Crop.DoneC"
},
{
"path": "lib/src/main/res/values-ar/strings.xml",
"chars": 366,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n <string name=\"crop__saving\">جارى حفظ الصورة …</string>\n "
},
{
"path": "lib/src/main/res/values-ca/strings.xml",
"chars": 391,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n <string name=\"crop__saving\">Guardant imatge…</string>\n "
},
{
"path": "lib/src/main/res/values-de/strings.xml",
"chars": 385,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n <string name=\"crop__saving\">Bild speichern…</string>\n "
},
{
"path": "lib/src/main/res/values-es/strings.xml",
"chars": 387,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n <string name=\"crop__saving\">Guardando imagen…</string>\n "
},
{
"path": "lib/src/main/res/values-fa/strings.xml",
"chars": 377,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n <string name=\"crop__saving\">در حال ذخیره سازی</string>\n "
},
{
"path": "lib/src/main/res/values-fr/strings.xml",
"chars": 395,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n <string name=\"crop__saving\">Enregistrement de l\\'image…<"
},
{
"path": "lib/src/main/res/values-in/strings.xml",
"chars": 392,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n <string name=\"crop__saving\">Menyimpan gambar…</string>\n "
},
{
"path": "lib/src/main/res/values-it/strings.xml",
"chars": 390,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n <string name=\"crop__saving\">Salvataggio immagine…</strin"
},
{
"path": "lib/src/main/res/values-ja/strings.xml",
"chars": 340,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n <string name=\"crop__saving\">保存中…</string>\n <string na"
},
{
"path": "lib/src/main/res/values-ko/strings.xml",
"chars": 352,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n <string name=\"crop__saving\">사진을 저장중입니다…</string>\n <st"
},
{
"path": "lib/src/main/res/values-land/dimens.xml",
"chars": 74,
"preview": "<resources>\n\n <dimen name=\"crop__bar_height\">48dp</dimen>\n\n</resources>"
},
{
"path": "lib/src/main/res/values-large/dimens.xml",
"chars": 74,
"preview": "<resources>\n\n <dimen name=\"crop__bar_height\">64dp</dimen>\n\n</resources>"
},
{
"path": "lib/src/main/res/values-pt/strings.xml",
"chars": 396,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n <string name=\"crop__saving\">Salvando imagem…</string>\n "
},
{
"path": "lib/src/main/res/values-ru/strings.xml",
"chars": 394,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n <string name=\"crop__saving\">Изображение сохраняется…</st"
},
{
"path": "lib/src/main/res/values-sv/strings.xml",
"chars": 375,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n <string name=\"crop__saving\">Sparar bild…</string>\n <s"
},
{
"path": "lib/src/main/res/values-tr/strings.xml",
"chars": 378,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n <string name=\"crop__saving\">Fotoğraf kaydediliyor…</stri"
},
{
"path": "lib/src/main/res/values-v21/colors.xml",
"chars": 81,
"preview": "<resources>\n\n <color name=\"crop__selector_pressed\">#aaaaaa</color>\n\n</resources>"
},
{
"path": "lib/src/main/res/values-zh-rCN/strings.xml",
"chars": 331,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n <string name=\"crop__saving\">正在保存照片…</string>\n <string"
},
{
"path": "lib/src/main/res/values-zh-rTW/strings.xml",
"chars": 335,
"preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n <string name=\"crop__saving\">正在儲存相片…</string>\n <string"
},
{
"path": "settings.gradle",
"chars": 27,
"preview": "include ':lib', ':example'\n"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the jdamcd/android-crop GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 63 files (101.6 KB), approximately 26.0k tokens, and a symbol index with 183 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.