callback) {
@Override
protected Bitmap doInBackground(AsyncTaskParams... params) {
Bitmap backgroundBitmap = params[0].getBackgroundImg();
- String watermarkString = params[0].getWatermarkText();
+ WatermarkText watermarkText = params[0].getWatermarkText();
Bitmap watermarkBitmap = params[0].getWatermarkImg();
+ String watermarkString;
if (backgroundBitmap == null) {
listener.onFailure(ERROR_NO_BACKGROUND);
@@ -68,6 +70,8 @@ protected Bitmap doInBackground(AsyncTaskParams... params) {
// convert the watermark bitmap into a String.
if (watermarkBitmap != null) {
watermarkString = BitmapUtils.bitmapToString(watermarkBitmap);
+ } else {
+ watermarkString = watermarkText.getText();
}
if (watermarkString == null) {
@@ -79,7 +83,7 @@ protected Bitmap doInBackground(AsyncTaskParams... params) {
backgroundBitmap.getConfig());
int[] backgroundPixels = getBitmapPixels(backgroundBitmap);
- int[] backgroundColorArray = bitmap2ARGBArray(backgroundPixels);
+ int[] backgroundColorArray = pixel2ARGBArray(backgroundPixels);
// convert the Sting into a binary string, and, replace the single digit number.
// using the rebuilt pixels to create a new watermarked image.
diff --git a/androidwm/src/main/java/com/watermark/androidwm/utils/BitmapUtils.java b/androidwm/src/main/java/com/watermark/androidwm/utils/BitmapUtils.java
index ea33895..13ca580 100644
--- a/androidwm/src/main/java/com/watermark/androidwm/utils/BitmapUtils.java
+++ b/androidwm/src/main/java/com/watermark/androidwm/utils/BitmapUtils.java
@@ -27,7 +27,7 @@
import android.graphics.Rect;
import android.graphics.Typeface;
import android.os.Environment;
-import android.support.v4.content.res.ResourcesCompat;
+import androidx.core.content.res.ResourcesCompat;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.Base64;
@@ -227,7 +227,7 @@ public static int[] getBitmapPixels(Bitmap inputBitmap) {
/**
* Bitmap to Pixels then converting it to an ARGB int array.
*/
- public static int[] bitmap2ARGBArray(int[] inputPixels) {
+ public static int[] pixel2ARGBArray(int[] inputPixels) {
int[] bitmapArray = new int[4 * inputPixels.length];
for (int i = 0; i < inputPixels.length; i++) {
bitmapArray[4 * i] = Color.alpha(inputPixels[i]);
diff --git a/androidwm/src/main/java/com/watermark/androidwm/utils/Constant.java b/androidwm/src/main/java/com/watermark/androidwm/utils/Constant.java
index 5d28ccb..f1063b2 100644
--- a/androidwm/src/main/java/com/watermark/androidwm/utils/Constant.java
+++ b/androidwm/src/main/java/com/watermark/androidwm/utils/Constant.java
@@ -23,15 +23,13 @@
*/
public class Constant {
public static final String LSB_IMG_PREFIX_FLAG = "1212";
- public static final String FD_IMG_PREFIX_FLAG = "1122";
public static final String LSB_TEXT_PREFIX_FLAG = "2323";
- public static final String FD_TEXT_PREFIX_FLAG = "3322";
public static final String LSB_IMG_SUFFIX_FLAG = "3434";
- public static final String FD_IMG_SUFFIX_FLAG = "3344";
public static final String LSB_TEXT_SUFFIX_FLAG = "4545";
- public static final String FD_TEXT_SUFFIX_FLAG = "5544";
public static final int MAX_IMAGE_SIZE = 1024;
+ // use the watermark image's size
+ public static final int CHUNK_SIZE = 5000;
public static final String ERROR_NO_WATERMARKS = "No input text or image! please load an image or a text in your WatermarkBuilder!";
public static final String ERROR_CREATE_FAILED = "created watermark failed!";
@@ -42,4 +40,6 @@ public class Constant {
public static final String ERROR_DETECT_FAILED = "Failed to detect the watermark!";
public static final String ERROR_NO_WATERMARK_FOUND = "No watermarks found in this image!";
public static final String ERROR_BITMAP_NULL = "Cannot detect the watermark! markedBitmap is null object!";
+
+ public static final String WARNING_BIG_IMAGE = "The input image may be too large to put into the memory, please be careful of the OOM!";
}
diff --git a/androidwm/src/main/java/com/watermark/androidwm/utils/FastDctFft.java b/androidwm/src/main/java/com/watermark/androidwm/utils/FastDctFft.java
new file mode 100644
index 0000000..f68e3ba
--- /dev/null
+++ b/androidwm/src/main/java/com/watermark/androidwm/utils/FastDctFft.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2018 Yizheng Huang
+ *
+ * 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.watermark.androidwm.utils;
+
+
+import java.util.Arrays;
+import java.util.Objects;
+
+public final class FastDctFft {
+
+ /**
+ * Computes the unscaled DCT type II on the specified array in place.
+ * The array length must be a power of 2 or zero.
+ *
+ * @param vector the vector of numbers to transform
+ * @throws NullPointerException if the array is {@code null}
+ */
+ public static void transform(double[] vector) {
+ Objects.requireNonNull(vector);
+ int len = vector.length;
+ int halfLen = len / 2;
+ double[] real = new double[len];
+
+ for (int i = 0; i < halfLen; i++) {
+ real[i] = vector[i * 2];
+ real[len - 1 - i] = vector[i * 2 + 1];
+ }
+
+ if (len % 2 == 1) {
+ real[halfLen] = vector[len - 1];
+ }
+
+ Arrays.fill(vector, 0.0);
+ Fft.transform(real, vector);
+ for (int i = 0; i < len; i++) {
+ double temp = i * Math.PI / (len * 2);
+ vector[i] = real[i] * Math.cos(temp) + vector[i] * Math.sin(temp);
+ }
+ }
+
+
+ /**
+ * Computes the unscaled DCT type III on the specified array in place.
+ * The array length must be a power of 2 or zero.
+ *
+ * @param vector the vector of numbers to transform
+ * @throws NullPointerException if the array is {@code null}
+ */
+ public static void inverseTransform(double[] vector) {
+ Objects.requireNonNull(vector);
+ int len = vector.length;
+ if (len > 0) {
+ vector[0] = vector[0] / 2;
+ }
+
+ double[] real = new double[len];
+
+ for (int i = 0; i < len; i++) {
+ double temp = i * Math.PI / (len * 2);
+ real[i] = vector[i] * Math.cos(temp);
+ vector[i] *= -Math.sin(temp);
+ }
+
+ Fft.transform(real, vector);
+
+ int halfLen = len / 2;
+ for (int i = 0; i < halfLen; i++) {
+ vector[i * 2] = real[i];
+ vector[i * 2 + 1] = real[len - 1 - i];
+ }
+
+ if (len % 2 == 1) {
+ vector[len - 1] = real[halfLen];
+ }
+
+ double scale = (double) len / 2;
+ for (int i = 0; i < len; i++) {
+ vector[i] = (int) Math.round(vector[i] / scale);
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/androidwm/src/main/java/com/watermark/androidwm/utils/Fft.java b/androidwm/src/main/java/com/watermark/androidwm/utils/Fft.java
new file mode 100644
index 0000000..7a4d3a7
--- /dev/null
+++ b/androidwm/src/main/java/com/watermark/androidwm/utils/Fft.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2018 Yizheng Huang
+ *
+ * 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.watermark.androidwm.utils;
+
+
+public final class Fft {
+
+ /**
+ * Computes the discrete Fourier transform (DFT) of the given complex vector,
+ * storing the result back into the vector.
+ *
+ * The vector can have any length. This is a wrapper function.
+ */
+ public static void transform(double[] real, double[] imag) {
+ int n = real.length;
+ if (n != imag.length) {
+ throw new IllegalArgumentException("Mismatched lengths");
+ }
+ if ((n & (n - 1)) == 0) {
+ transformRadix2(real, imag);
+ } else {
+ transformBlueStein(real, imag);
+ }
+
+ }
+
+
+ /**
+ * Computes the inverse discrete Fourier transform (IDFT) of the given
+ * complex vector, storing the result back into the vector.
+ *
+ * The vector can have any length. This is a wrapper function.
+ * This transform does not perform scaling, so the inverse is not a true inverse.
+ */
+ private static void inverseTransform(double[] real, double[] imag) {
+ transform(imag, real);
+ }
+
+ /**
+ * Computes the discrete Fourier transform (DFT) of the given complex vector,
+ * storing the result back into the vector.
+ *
+ * The vector's length must be a power of 2. Uses the Cooley-Tukey
+ * decimation-in-time radix-2 algorithm.
+ */
+ private static void transformRadix2(double[] real, double[] imag) {
+ int n = real.length;
+ if (n != imag.length) {
+ throw new IllegalArgumentException("Mismatched lengths");
+ }
+
+ int levels = 31 - Integer.numberOfLeadingZeros(n);
+ if (1 << levels != n) {
+ throw new IllegalArgumentException("Length is not a power of 2");
+ }
+
+ double[] cosTable = new double[n / 2];
+ double[] sinTable = new double[n / 2];
+ for (int i = 0; i < n / 2; i++) {
+ cosTable[i] = Math.cos(2 * Math.PI * i / n);
+ sinTable[i] = Math.sin(2 * Math.PI * i / n);
+ }
+
+ for (int i = 0; i < n; i++) {
+ int j = Integer.reverse(i) >>> (32 - levels);
+ if (j > i) {
+ double temp = real[i];
+ real[i] = real[j];
+ real[j] = temp;
+ temp = imag[i];
+ imag[i] = imag[j];
+ imag[j] = temp;
+ }
+ }
+
+ for (int size = 2; size <= n; size *= 2) {
+ int halfSize = size / 2;
+ int tableStep = n / size;
+
+ for (int i = 0; i < n; i += size) {
+ for (int j = i, k = 0; j < i + halfSize; j++, k += tableStep) {
+ int l = j + halfSize;
+ double tpre = real[l] * cosTable[k] + imag[l] * sinTable[k];
+ double tpim = -real[l] * sinTable[k] + imag[l] * cosTable[k];
+ real[l] = real[j] - tpre;
+ imag[l] = imag[j] - tpim;
+ real[j] += tpre;
+ imag[j] += tpim;
+ }
+ }
+
+ if (size == n) {
+ break;
+ }
+
+ }
+ }
+
+
+ /**
+ * Computes the discrete Fourier transform (DFT) of the given complex vector,
+ * storing the result back into the vector.
+ *
+ * The vector can have any length. This requires the convolution function,
+ * which in turn requires the radix-2 FFT function.
+ *
+ * Uses Bluestein's chirp z-transform algorithm.
+ */
+ private static void transformBlueStein(double[] real, double[] imag) {
+ int n = real.length;
+ if (n != imag.length) {
+ throw new IllegalArgumentException("Mismatched lengths");
+ }
+ if (n >= 0x20000000) {
+ throw new IllegalArgumentException("Array too large");
+ }
+
+ int m = Integer.highestOneBit(n) * 4;
+
+ double[] cosTable = new double[n];
+ double[] sinTable = new double[n];
+ for (int i = 0; i < n; i++) {
+ int j = (int) ((long) i * i % (n * 2));
+ cosTable[i] = Math.cos(Math.PI * j / n);
+ sinTable[i] = Math.sin(Math.PI * j / n);
+ }
+
+ double[] aReal = new double[m];
+ double[] aImag = new double[m];
+ for (int i = 0; i < n; i++) {
+ aReal[i] = real[i] * cosTable[i] + imag[i] * sinTable[i];
+ aImag[i] = -real[i] * sinTable[i] + imag[i] * cosTable[i];
+ }
+ double[] bReal = new double[m];
+ double[] bImag = new double[m];
+ bReal[0] = cosTable[0];
+ bImag[0] = sinTable[0];
+ for (int i = 1; i < n; i++) {
+ bReal[i] = bReal[m - i] = cosTable[i];
+ bImag[i] = bImag[m - i] = sinTable[i];
+ }
+
+ double[] cReal = new double[m];
+ double[] cImag = new double[m];
+ convolve(aReal, aImag, bReal, bImag, cReal, cImag);
+
+ for (int i = 0; i < n; i++) {
+ real[i] = cReal[i] * cosTable[i] + cImag[i] * sinTable[i];
+ imag[i] = -cReal[i] * sinTable[i] + cImag[i] * cosTable[i];
+ }
+ }
+
+ /**
+ * Computes the circular convolution of the given complex vectors.
+ * Each vector's length must be the same.
+ */
+ private static void convolve(double[] xReal, double[] xImag,
+ double[] yReal, double[] yImag, double[] outReal, double[] outImag) {
+
+ int n = xReal.length;
+ if (n != xImag.length || n != yReal.length || n != yImag.length
+ || n != outReal.length || n != outImag.length) {
+ throw new IllegalArgumentException("Mismatched lengths");
+ }
+
+ xReal = xReal.clone();
+ xImag = xImag.clone();
+ yReal = yReal.clone();
+ yImag = yImag.clone();
+ transform(xReal, xImag);
+ transform(yReal, yImag);
+
+ for (int i = 0; i < n; i++) {
+ double temp = xReal[i] * yReal[i] - xImag[i] * yImag[i];
+ xImag[i] = xImag[i] * yReal[i] + xReal[i] * yImag[i];
+ xReal[i] = temp;
+ }
+
+ inverseTransform(xReal, xImag);
+
+ for (int i = 0; i < n; i++) {
+ outReal[i] = xReal[i] / n;
+ outImag[i] = xImag[i] / n;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/androidwm/src/main/java/com/watermark/androidwm/utils/StringUtils.java b/androidwm/src/main/java/com/watermark/androidwm/utils/StringUtils.java
index 6557545..f32fd1f 100644
--- a/androidwm/src/main/java/com/watermark/androidwm/utils/StringUtils.java
+++ b/androidwm/src/main/java/com/watermark/androidwm/utils/StringUtils.java
@@ -142,4 +142,17 @@ public static double[] copyFromIntArray(int[] source) {
return dest;
}
+ /**
+ * cast a double array to an int array.
+ * System.arrayCopy cannot cast the double array to an int one.
+ */
+ @SuppressWarnings("PMD")
+ public static int[] copyFromDoubleArray(double[] source) {
+ int[] dest = new int[source.length];
+ for (int i = 0; i < source.length; i++) {
+ dest[i] = (int) source[i];
+ }
+ return dest;
+ }
+
}
diff --git a/androidwm/src/test/java/com/watermark/androidwm/DCTTest.java b/androidwm/src/test/java/com/watermark/androidwm/DCTTest.java
new file mode 100644
index 0000000..27068ff
--- /dev/null
+++ b/androidwm/src/test/java/com/watermark/androidwm/DCTTest.java
@@ -0,0 +1,127 @@
+package com.watermark.androidwm;
+
+import com.watermark.androidwm.utils.FastDctFft;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+/**
+ * The unit tests for the FD invisible watermark detection methods.
+ *
+ * @author huangyz0918 (huangyz0918@gmail.com)
+ */
+public class DCTTest {
+
+ @Test
+ public void testFftDct() {
+ double[] test = {255.0, 254.0, 243.0, 253.0, 255.0, 255.0, 246.0, 255.0, 255.0, 255.0};
+ double[] temp = {255.0, 254.0, 243.0, 253.0, 255.0, 255.0, 246.0, 255.0, 255.0, 255.0};
+ FastDctFft.transform(test);
+ FastDctFft.inverseTransform(test);
+
+ assertEquals(temp[0] - test[0], 0, 0);
+ assertEquals(temp[1] - test[1], 0, 0);
+ assertEquals(temp[2] - test[2], 0, 0);
+ assertEquals(temp[3] - test[3], 0, 0);
+ }
+
+ @Test
+ public void testCombine() {
+ double[] a = {1, 2, 3, 4, 5};
+ double[] b = {6, 7, 8, 9, 0};
+ double[] c = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
+
+ FastDctFft.transform(a);
+ FastDctFft.transform(b);
+ FastDctFft.transform(c);
+
+ double[] combine = new double[10];
+ System.arraycopy(a, 0, combine, 0, 5);
+ System.arraycopy(b, 0, combine, 5, 5);
+
+ assertNotEquals(Arrays.toString(combine), Arrays.toString(c));
+ }
+
+ @Test
+ public void testDivide() {
+ boolean result = false;
+ int count = 0;
+ int[] test = new int[200];
+ for (int i = 0; i < test.length; i++) {
+ test[i] = 0;
+ }
+
+ int numOfChunks = (int) Math.ceil((double) test.length / 20);
+ for (int i = 0; i < numOfChunks; i++) {
+ int start = i * 20;
+ int length = Math.min(test.length - start, 20);
+ int[] temp = new int[length];
+ System.arraycopy(test, start, temp, 0, length);
+
+ for (int j = 0; j < temp.length; j++) {
+ temp[j] = 1;
+ count++;
+ test[start + j] = 1;
+ }
+ }
+
+ for (int i : test
+ ) {
+ if (i == 0) {
+ result = true;
+ }
+ }
+
+ assertEquals(result, false);
+ assertEquals(count, 200);
+
+ }
+
+ @Test
+ public void testDct() {
+ double[] test64 = {
+ 231.0, 224.0, 224.0, 217.0, 217.0, 203.0, 189.0, 196.0,
+ 210.0, 217.0, 203.0, 189.0, 203.0, 224.0, 217.0, 224.0,
+ 196.0, 217.0, 210.0, 224.0, 203.0, 203.0, 196.0, 189.0,
+ 210.0, 203.0, 196.0, 203.0, 182.0, 203.0, 182.0, 189.0,
+ 203.0, 224.0, 203.0, 217.0, 196.0, 175.0, 154.0, 140.0,
+ 182.0, 189.0, 168.0, 161.0, 154.0, 126.0, 119.0, 112.0,
+ 175.0, 154.0, 126.0, 105.0, 140.0, 105.0, 119.0, 84.0,
+ 154.0, 98.0, 105.0, 98.0, 105.0, 63.0, 112.0, 84.0};
+//
+// double[] temp = {
+// 231.0, 224.0, 224.0, 217.0, 217.0, 203.0, 189.0, 196.0,
+// 210.0, 217.0, 203.0, 189.0, 203.0, 224.0, 217.0, 224.0,
+// 196.0, 217.0, 210.0, 224.0, 203.0, 203.0, 196.0, 189.0,
+// 210.0, 203.0, 196.0, 203.0, 182.0, 203.0, 182.0, 189.0,
+// 203.0, 224.0, 203.0, 217.0, 196.0, 175.0, 154.0, 140.0,
+// 182.0, 189.0, 168.0, 161.0, 154.0, 126.0, 119.0, 112.0,
+// 175.0, 154.0, 126.0, 105.0, 140.0, 105.0, 119.0, 84.0,
+// 154.0, 98.0, 105.0, 98.0, 105.0, 63.0, 112.0, 84.0};
+
+ FastDctFft.transform(test64);
+
+ // the operations need to be done.
+
+ for (int j = 0; j < test64.length; j++) {
+ test64[j] = test64[j] * 2;
+ }
+
+ FastDctFft.inverseTransform(test64);
+//
+// for (int i = 0; i < test64.length; i++) {
+// temp[i] = test64[i] - temp[i];
+// }
+
+// for (double i : test64) {
+// System.out.println(i / 2);
+// }
+
+ }
+
+
+}
diff --git a/build.gradle b/build.gradle
index 509f5ff..814c6a2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -47,10 +47,10 @@ subprojects { project ->
xml.enabled = false
html.enabled = true
xml {
- destination "$project.buildDir/reports/pmd/pmd.xml"
+ destination file("$project.buildDir/reports/pmd/pmd.xml")
}
html {
- destination "$project.buildDir/reports/pmd/pmd.html"
+ destination file("$project.buildDir/reports/pmd/pmd.html")
}
}
}
@@ -65,7 +65,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.1.4'
+ classpath 'com.android.tools.build:gradle:4.1.3'
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4'
diff --git a/gradle.properties b/gradle.properties
index 743d692..04cd79b 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -6,8 +6,10 @@
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
-org.gradle.jvmargs=-Xmx1536m
+org.gradle.jvmargs=-Xmx1536m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 9fffb11..b93f580 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Tue Aug 28 15:10:25 CST 2018
+#Wed Jan 09 11:07:57 CST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
diff --git a/images/banner.svg b/images/banner.svg
new file mode 100644
index 0000000..372e7ca
--- /dev/null
+++ b/images/banner.svg
@@ -0,0 +1,224 @@
+
+
+
+
diff --git a/images/logo.svg b/images/logo.svg
new file mode 100644
index 0000000..1a3dfb3
--- /dev/null
+++ b/images/logo.svg
@@ -0,0 +1,165 @@
+
+
+
+
diff --git a/sample/build.gradle b/sample/build.gradle
index 19c5b2d..d40ced0 100644
--- a/sample/build.gradle
+++ b/sample/build.gradle
@@ -1,14 +1,14 @@
apply plugin: 'com.android.application'
android {
- compileSdkVersion 28
+ compileSdkVersion 31
defaultConfig {
applicationId "com.watermark.androidwm.sample"
minSdkVersion 16
- targetSdkVersion 28
+ targetSdkVersion 31
versionCode 1
versionName "1.0"
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
buildTypes {
release {
@@ -25,11 +25,11 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
- implementation 'com.android.support:appcompat-v7:28.+'
- implementation 'com.android.support.constraint:constraint-layout:1.1.2'
+ implementation 'androidx.appcompat:appcompat:1.0.0'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
- androidTestImplementation 'com.android.support.test:runner:1.0.2'
- androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.1'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
// test for local library.
implementation project(":androidwm")
// glide
diff --git a/sample/src/androidTest/java/com/watermark/androidwm/sample/ExampleInstrumentedTest.java b/sample/src/androidTest/java/com/watermark/androidwm/sample/ExampleInstrumentedTest.java
index e4f0b40..67a7109 100644
--- a/sample/src/androidTest/java/com/watermark/androidwm/sample/ExampleInstrumentedTest.java
+++ b/sample/src/androidTest/java/com/watermark/androidwm/sample/ExampleInstrumentedTest.java
@@ -1,8 +1,8 @@
package com.watermark.androidwm.sample;
import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -19,7 +19,7 @@ public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
- Context appContext = InstrumentationRegistry.getTargetContext();
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.androidwm.watermark.androidwm", appContext.getPackageName());
}
diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml
index 1114ab9..cf6c2d4 100644
--- a/sample/src/main/AndroidManifest.xml
+++ b/sample/src/main/AndroidManifest.xml
@@ -12,7 +12,8 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
-
+
diff --git a/sample/src/main/java/com/watermark/androidwm/sample/MainActivity.java b/sample/src/main/java/com/watermark/androidwm/sample/MainActivity.java
index c4287e7..03d3341 100644
--- a/sample/src/main/java/com/watermark/androidwm/sample/MainActivity.java
+++ b/sample/src/main/java/com/watermark/androidwm/sample/MainActivity.java
@@ -19,16 +19,20 @@
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
-import android.support.v7.app.AppCompatActivity;
+import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
+
+import android.widget.ProgressBar;
+import android.widget.RadioButton;
import android.widget.Toast;
import com.bumptech.glide.Glide;
import com.watermark.androidwm.WatermarkDetector;
+import com.watermark.androidwm.bean.WatermarkPosition;
import com.watermark.androidwm.task.DetectionReturnValue;
import com.watermark.androidwm.listener.BuildFinishListener;
import com.watermark.androidwm.WatermarkBuilder;
@@ -47,10 +51,11 @@
*/
public class MainActivity extends AppCompatActivity {
+ private RadioButton mode_single;
+ private RadioButton mode_tile;
+ private RadioButton mode_invisible;
private Button btnAddText;
private Button btnAddImg;
- private Button btnAddInVisibleImage;
- private Button btnAddInvisibleText;
private Button btnDetectImage;
private Button btnDetectText;
private Button btnClear;
@@ -61,6 +66,8 @@ public class MainActivity extends AppCompatActivity {
private EditText editText;
+ private ProgressBar progressBar;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -70,10 +77,11 @@ protected void onCreate(Bundle savedInstanceState) {
}
private void initViews() {
+ mode_single = findViewById(R.id.mode_single);
+ mode_tile = findViewById(R.id.mode_tile);
+ mode_invisible = findViewById(R.id.mode_invisible);
btnAddImg = findViewById(R.id.btn_add_image);
btnAddText = findViewById(R.id.btn_add_text);
- btnAddInVisibleImage = findViewById(R.id.btn_add_invisible_image);
- btnAddInvisibleText = findViewById(R.id.btn_add_invisible_text);
btnDetectImage = findViewById(R.id.btn_detect_image);
btnDetectText = findViewById(R.id.btn_detect_text);
btnClear = findViewById(R.id.btn_clear_watermark);
@@ -82,6 +90,8 @@ private void initViews() {
backgroundView = findViewById(R.id.imageView);
watermarkView = findViewById(R.id.imageView_watermark);
+ progressBar = findViewById(R.id.progressBar);
+
watermarkBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.test_watermark);
@@ -91,16 +101,26 @@ private void initViews() {
private void initEvents() {
// The sample method of adding a text watermark.
btnAddText.setOnClickListener((View v) -> {
- WatermarkText watermarkText = new WatermarkText(editText.getText().toString())
- .setPositionX(0.5)
- .setPositionY(0.5)
+ String markText = editText.getText().toString();
+ if(markText.trim().isEmpty()){
+ Toast.makeText(this, "Please Input Text First", Toast.LENGTH_SHORT).show();
+ return;
+ }
+ if(mode_invisible.isChecked()){
+ createInvisibleTextMark();
+ return;
+ }
+ WatermarkText watermarkText = new WatermarkText(markText)
+ .setPosition(new WatermarkPosition(0.5, 0.5))
+ .setOrigin(new WatermarkPosition(0.5, 0.5))
+ .setTextSize(40)
.setTextAlpha(255)
.setTextColor(Color.WHITE)
.setTextFont(R.font.champagne)
.setTextShadow(0.1f, 5, 5, Color.BLUE);
WatermarkBuilder.create(this, backgroundView)
- .setTileMode(true)
+ .setTileMode(mode_tile.isChecked())
.loadWatermarkText(watermarkText)
.getWatermark()
.setToImageView(backgroundView);
@@ -108,77 +128,37 @@ private void initEvents() {
// The sample method of adding an image watermark.
btnAddImg.setOnClickListener((View v) -> {
-
+ if(mode_invisible.isChecked()){
+ createInvisibleImgMark();
+ return;
+ }
// Math.random()
WatermarkImage watermarkImage = new WatermarkImage(watermarkBitmap)
.setImageAlpha(80)
.setPositionX(Math.random())
.setPositionY(Math.random())
+ // .setPosition(new WatermarkPosition(0.5, 0.5))
+ .setOrigin(new WatermarkPosition(0.5, 0.5))
.setRotation(15)
.setSize(0.1);
WatermarkBuilder
.create(this, backgroundView)
.loadWatermarkImage(watermarkImage)
- .setTileMode(true)
+ .setTileMode(mode_tile.isChecked())
.getWatermark()
.setToImageView(backgroundView);
});
- // The sample method of adding an invisible image watermark.
- btnAddInVisibleImage.setOnClickListener((View v) -> {
- WatermarkBuilder
- .create(this, backgroundView)
- .loadWatermarkImage(watermarkBitmap)
- .setInvisibleWMListener(true, new BuildFinishListener() {
- @Override
- public void onSuccess(Bitmap object) {
- Toast.makeText(MainActivity.this,
- "Successfully create invisible watermark!", Toast.LENGTH_SHORT).show();
- if (object != null) {
- backgroundView.setImageBitmap(object);
- // Save to local needs permission.
-// BitmapUtils.saveAsPNG(object, "sdcard/DCIM/", true);
- }
- }
-
- @Override
- public void onFailure(String message) {
- Timber.e(message);
- }
- });
- });
-
- // The sample method of adding an invisible text watermark.
- btnAddInvisibleText.setOnClickListener((View v) -> {
- WatermarkText watermarkText = new WatermarkText(editText.getText().toString());
- WatermarkBuilder
- .create(this, backgroundView)
- .loadWatermarkText(watermarkText)
- .setInvisibleWMListener(true, new BuildFinishListener() {
- @Override
- public void onSuccess(Bitmap object) {
- Toast.makeText(MainActivity.this,
- "Successfully create invisible watermark!", Toast.LENGTH_SHORT).show();
- if (object != null) {
- backgroundView.setImageBitmap(object);
- }
- }
-
- @Override
- public void onFailure(String message) {
- Timber.e(message);
- }
- });
- });
-
// detect the text watermark.
btnDetectText.setOnClickListener((View v) -> {
+ progressBar.setVisibility(View.VISIBLE);
WatermarkDetector.create(backgroundView, true)
.detect(new DetectFinishListener() {
@Override
public void onSuccess(DetectionReturnValue returnValue) {
+ progressBar.setVisibility(View.GONE);
if (returnValue != null) {
Toast.makeText(MainActivity.this, "Successfully detected invisible text: "
+ returnValue.getWatermarkString(), Toast.LENGTH_SHORT).show();
@@ -187,6 +167,7 @@ public void onSuccess(DetectionReturnValue returnValue) {
@Override
public void onFailure(String message) {
+ progressBar.setVisibility(View.GONE);
Timber.e(message);
}
});
@@ -194,10 +175,12 @@ public void onFailure(String message) {
// detect the image watermark.
btnDetectImage.setOnClickListener((View v) -> {
+ progressBar.setVisibility(View.VISIBLE);
WatermarkDetector.create(backgroundView, true)
.detect(new DetectFinishListener() {
@Override
public void onSuccess(DetectionReturnValue returnValue) {
+ progressBar.setVisibility(View.GONE);
Toast.makeText(MainActivity.this,
"Successfully detected invisible watermark!", Toast.LENGTH_SHORT).show();
if (returnValue != null) {
@@ -208,6 +191,7 @@ public void onSuccess(DetectionReturnValue returnValue) {
@Override
public void onFailure(String message) {
+ progressBar.setVisibility(View.GONE);
Timber.e(message);
}
});
@@ -215,10 +199,61 @@ public void onFailure(String message) {
// reload the background.
btnClear.setOnClickListener((View v) -> {
- Glide.with(this).load(R.drawable.test)
+ Glide.with(this).load(R.drawable.test2)
.into(backgroundView);
watermarkView.setVisibility(View.GONE);
});
}
+
+ private void createInvisibleImgMark(){
+ progressBar.setVisibility(View.VISIBLE);
+ WatermarkBuilder
+ .create(this, backgroundView)
+ .loadWatermarkImage(watermarkBitmap)
+ .setInvisibleWMListener(true, new BuildFinishListener() {
+ @Override
+ public void onSuccess(Bitmap object) {
+ progressBar.setVisibility(View.GONE);
+ Toast.makeText(MainActivity.this,
+ "Successfully create invisible watermark!", Toast.LENGTH_SHORT).show();
+ if (object != null) {
+ backgroundView.setImageBitmap(object);
+ // Save to local needs permission.
+// BitmapUtils.saveAsPNG(object, "sdcard/DCIM/", true);
+ }
+ }
+
+ @Override
+ public void onFailure(String message) {
+ progressBar.setVisibility(View.GONE);
+ Timber.e(message);
+ }
+ });
+ }
+
+ private void createInvisibleTextMark(){
+ progressBar.setVisibility(View.VISIBLE);
+ WatermarkText watermarkText = new WatermarkText(editText.getText().toString());
+ WatermarkBuilder
+ .create(this, backgroundView)
+ .loadWatermarkText(watermarkText)
+ .setInvisibleWMListener(true, new BuildFinishListener() {
+ @Override
+ public void onSuccess(Bitmap object) {
+ progressBar.setVisibility(View.GONE);
+ Toast.makeText(MainActivity.this,
+ "Successfully create invisible watermark!", Toast.LENGTH_SHORT).show();
+ if (object != null) {
+ backgroundView.setImageBitmap(object);
+ }
+ }
+
+ @Override
+ public void onFailure(String message) {
+ progressBar.setVisibility(View.GONE);
+ Timber.e(message);
+ }
+ });
+ }
}
diff --git a/sample/src/main/res/drawable/test2.jpg b/sample/src/main/res/drawable/test2.jpg
new file mode 100644
index 0000000..5ec6cc7
Binary files /dev/null and b/sample/src/main/res/drawable/test2.jpg differ
diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml
index aa0c31d..14104e5 100644
--- a/sample/src/main/res/layout/activity_main.xml
+++ b/sample/src/main/res/layout/activity_main.xml
@@ -6,12 +6,19 @@
android:orientation="vertical"
tools:context="com.watermark.androidwm.sample.MainActivity">
+
+
+ android:src="@drawable/test2" />
-
-
-
-
+ android:orientation="horizontal">
+
+
+
+
+
+
+
+
+
+
+
+ android:text="@string/btn_add_image" />
+ android:text="@string/btn_add_text" />
diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml
index 23d0e07..bf3aa52 100644
--- a/sample/src/main/res/values/strings.xml
+++ b/sample/src/main/res/values/strings.xml
@@ -8,5 +8,5 @@
detect text
Clear watermarks
Load picture
- Add a text for invisible watermark
+ input mark text here
diff --git a/wikis/WIKI-CN.md b/wikis/WIKI-CN.md
new file mode 100644
index 0000000..e5714da
--- /dev/null
+++ b/wikis/WIKI-CN.md
@@ -0,0 +1,106 @@
+# 使用说明
+
+## 水印位置
+我们使用 `WatermarkPosition` 这个类的对象来控制具体水印出现的位置。
+
+```java
+ WatermarkPosition watermarkPosition = new WatermarkPosition(double position_x, double position_y, double rotation);
+ WatermarkPosition watermarkPosition = new WatermarkPosition(double position_x, double position_y);
+```
+
+在函数构造器中,我们可以设定水印图片的横纵坐标,如果你想在构造器中初始化一个水印旋转角度也是可以的, 水印的坐标系以背景图片的左上角为原点,横轴向右,纵轴向下。
+如果需要将定位原点从左上角修改为其他位置,可以稍后给`WatermarkText`或`WatermarkImage`调用`setOrigin(new WatermarkPosition(0.5, 0.5))`方法,
+这里x,y都是0到1的浮点数,默认都是0,表示左上角对齐;0.5,0.5则表示中心对齐。
+
+`WatermarkPosition` 同时也支持动态调整水印的位置,这样你就不需要一次又一次地初始化新的位置对象了, androidwm 提供了一些方法:
+
+```java
+ watermarkPosition
+ .setPositionX(x)
+ .setPositionY(y)
+ .setRotation(rotation);
+```
+在全覆盖水印模式(Tile mode)下,关于水印位置的参数将会失效。
+
+|  |  |
+| :-------------: | :-------------: |
+| x = y = 0, rotation = 15 | x = y = 0.5, rotation = -15 |
+
+横纵坐标都是一个从 0 到 1 的浮点数,代表着和背景图片的相对比例。
+
+
+## 字体水印的颜色
+
+你可以在 `WatermarkText` 中设置字体水印的颜色或者是其背景颜色:
+
+```java
+ WatermarkText watermarkText = new WatermarkText(editText)
+ .setPositionX(0.5)
+ .setPositionY(0.5)
+ .setTextSize(30)
+ .setTextAlpha(200)
+ .setTextColor(Color.GREEN)
+ .setBackgroundColor(Color.WHITE); // 默认背景颜色是透明的
+```
+
+|  |  |
+| :-------------: | :-------------: |
+| color = green, background color = white | color = green, background color = default |
+
+## 字体颜色的阴影和字体
+你可以从软件资源中加载一种字体,也可以通过方法 `setTextShadow` 设置字体的阴影。
+
+```java
+ WatermarkText watermarkText = new WatermarkText(editText)
+ .setPositionX(0.5)
+ .setPositionY(0.5)
+ .setOrigin(new WatermarkPosition(0.5, 0.5))
+ .setTextSize(40)
+ .setTextAlpha(200)
+ .setTextColor(Color.GREEN)
+ .setTextFont(R.font.champagne)
+ .setTextShadow(0.1f, 5, 5, Color.BLUE);
+```
+
+|  |  |
+| :-------------: | :-------------: |
+| font = champagne | shadow = (0.1f, 5, 5, BLUE) |
+
+阴影的四个参数分别为: `(blur radius, x offset, y offset, color)`.
+
+## 字体大小和图片大小
+
+水印字体和水印图片大小的单位是不同的:
+- 字体大小和系统布局中字体大小是类似的,取决于屏幕的分辨率和背景图片的像素,您可能需要动态调整。
+- 图片大小是一个从 0 到 1 的浮点数,是水印图片的宽度占背景图片宽度的比例。
+
+|  |  |
+| :-------------: | :-------------: |
+| image size = 0.3 | text size = 40 |
+
+
+## 方法列表
+对于 `WatermarkText` 和 `WatermarkImage` 的定制化,我们提供了一些常用的方法:
+
+
+| __方法名称__ | __备注__ | __默认值__ |
+| ------------- | ------------- | ------------- |
+| setPosition | 水印的位置类 `WatermarkPosition` | _null_ |
+| setPositionX | 水印的横轴坐标,从背景图片左上角为(0,0) | _0_ |
+| setPositionY | 水印的纵轴坐标,从背景图片左上角为(0,0) | _0_ |
+| setRotation | 水印的旋转角度| _0_ |
+| setOrigin | 水印的对齐原点| _null_ |
+| setOriginX | 水印的横坐标对齐位置,0~1之间| _0_ |
+| setOriginY | 水印的纵坐标对齐位置,0~1之间| _0_ |
+| setTextColor (`WatermarkText`) | `WatermarkText` 的文字颜色 | _`Color.BLACK`_ |
+| setTextStyle (`WatermarkText`) | `WatermarkText` 的文字样式| _`Paint.Style.FILL`_ |
+| setBackgroundColor (`WatermarkText`) | `WatermarkText` 的背景颜色 | _null_ |
+| setTextAlpha (`WatermarkText`) | `WatermarkText` 文字的透明度, 从 0 到 255 | _50_ |
+| setImageAlpha (`WatermarkImage`) | `WatermarkImage` 图片的透明度, 从 0 到 255 | _50_ |
+| setTextSize (`WatermarkText`) | `WatermarkText` 字体的大小,单位与系统 layout 相同 | _20_ |
+| setSize (`WatermarkImage`)| `WatermarkImage` 水印图片的大小,从 0 到 1 (背景图片大小的比例) | _0.2_ |
+| setTextFont (`WatermarkText`) | `WatermarkText` 的字体| _default_ |
+| setTextShadow (`WatermarkText`)| `WatermarkText` 字体的阴影与圆角 | _(0, 0, 0)_ |
+| setImageDrawable (`WatermarkImage`)| `WatermarkImage`的图片资源 | _null_ |
+
+`WatermarkImage` 的一些基本属性和`WatermarkText` 的相同。
diff --git a/wikis/WIKI.md b/wikis/WIKI.md
new file mode 100644
index 0000000..7a5851c
--- /dev/null
+++ b/wikis/WIKI.md
@@ -0,0 +1,107 @@
+# Detailed Usages
+
+## Position
+
+We use the class `WatermarkPosition` to control the position of a watermark.
+
+```java
+ WatermarkPosition watermarkPosition = new WatermarkPosition(double position_x, double position_y, double rotation);
+ WatermarkPosition watermarkPosition = new WatermarkPosition(double position_x, double position_y);
+```
+
+We can set the abscissa and ordinate of the watermark in the constructor, or you can optionally add the rotation angle. The coordinate system starts from the upper left corner of the background image, and the upper left corner is the origin.
+
+If you need to change the align origin point from the upper left corner to another position, you can call the `setOrigin(new WatermarkPosition(0.5, 0.5))` method for `WatermarkText` or `WatermarkImage` later,
+Here x, y are both floating point numbers from 0 to 1. The default is 0, which means align to the upper left corner; 0.5, 0.5 means aligns to the absolute center.
+
+The `WatermarkPosition` also supports change the position dynamically, androidwm offers several methods for you to modify the positions.
+
+```java
+ watermarkPosition
+ .setPositionX(x)
+ .setPositionY(y)
+ .setRotation(rotation);
+```
+In full coverage mode (Tile mode), the positional parameters of the watermark image will be invalid.
+
+|  |  |
+| :-------------: | :-------------: |
+| x = y = 0, rotation = 15 | x = y = 0.5, rotation = -15 |
+
+Both the abscissa and the ordinate are a number from 0 to 1, representing a fixed proportion of the background image.
+
+
+## Color of Text Watermark
+
+You can set the text color and background color in `WatermarkText`:
+
+```java
+ WatermarkText watermarkText = new WatermarkText(editText)
+ .setPositionX(0.5)
+ .setPositionY(0.5)
+ .setOrigin(new WatermarkPosition(0.5, 0.5))
+ .setTextSize(30)
+ .setTextAlpha(200)
+ .setTextColor(Color.GREEN)
+ .setBackgroundColor(Color.WHITE); // if not, the background color will be transparent
+```
+
+|  |  |
+| :-------------: | :-------------: |
+| color = green, background color = white | color = green, background color = default |
+
+## Text Shadow and Font
+You can set the text font by loading a local resource, besides, you can also set the shadow by `setTextShadow`.
+
+```java
+ WatermarkText watermarkText = new WatermarkText(editText)
+ .setPositionX(0.5)
+ .setPositionY(0.5)
+ .setTextSize(40)
+ .setTextAlpha(200)
+ .setTextColor(Color.GREEN)
+ .setTextFont(R.font.champagne)
+ .setTextShadow(0.1f, 5, 5, Color.BLUE);
+```
+
+|  |  |
+| :-------------: | :-------------: |
+| font = champagne | shadow = (0.1f, 5, 5, BLUE) |
+
+the four parameters of text shadow is: `(blur radius, x offset, y offset, color)`.
+
+## Text Size and Image Size
+
+The text font size unit and the image size unit are different:
+- Text size is as the same unit as the android layout. (will auto adjust with the screen density and background pixels)
+- The image size is from 0 to 1, which is the width of the background image percentage.
+
+|  |  |
+| :-------------: | :-------------: |
+| image size = 0.3 | text size = 40 |
+
+## Table of Methods
+
+Here is a table of attributes in `WatermarkText` and `WatermarkImage` that you can custom with:
+
+| __Method__ | __Description__ | __Default value__ |
+| ------------- | ------------- | ------------- |
+| setPosition | `WatermarkPosition` for the watermark | _null_ |
+| setPositionX | the x-axis coordinates of the watermark | _0_ |
+| setPositionY | the y-axis coordinates of the watermark | _0_ |
+| setRotation | the rotation of the watermark| _0_ |
+| setOrigin | the align origin of the watermark| _null_ |
+| setOriginX | the abscissa align position of the watermark, between 0 and 1| _0_ |
+| setOriginY | the ordinate align position of the watermark, between 0 and 1| _0_ |
+| setTextColor (`WatermarkText`) | the text color of the `WatermarkText` | _`Color.BLACK`_ |
+| setTextStyle (`WatermarkText`) | the text style of the `WatermarkText` | _`Paint.Style.FILL`_ |
+| setBackgroundColor (`WatermarkText`) | the background color of the `WatermarkText` | _null_ |
+| setTextAlpha (`WatermarkText`) | the text alpha of the `WatermarkText`, from 0 to 255 | _50_ |
+| setImageAlpha (`WatermarkImage`) | the text alpha of the `WatermarkImage`, from 0 to 255 | _50_ |
+| setTextSize (`WatermarkText`) | the text size of the `WatermarkText`, consistent with the size unit used by the layout | _20_ |
+| setSize (`WatermarkImage`)| the image size of the `WatermarkImage`, from 0 to 1 (the proportion of background size) | _0.2_ |
+| setTextFont (`WatermarkText`) | typeface of the `WatermarkText` | _default_ |
+| setTextShadow (`WatermarkText`)| shadow of the `WatermarkText` | _(0, 0, 0)_ |
+| setImageDrawable (`WatermarkImage`)| image drawable of the `WatermarkImage` | _null_
+
+The basic methods of `WatermarkImage` are the same as `WatermarkText`.