42 | * This is the native version. 43 | */ 44 | public static native String stringToBinary(String inputText); 45 | 46 | /** 47 | * String to integer array. 48 | *
49 | * This is the native version.
50 | */
51 | public static native int[] stringToIntArray(String inputString);
52 |
53 | /**
54 | * Converting a binary string to a ASCII string.
55 | */
56 | public static native String binaryToString(String inputText);
57 |
58 | /**
59 | * Replace the wrong rgb number in a form of binary,
60 | * the only case is 0 - 1 = 9, so, we need to replace
61 | * all nines to zero.
62 | */
63 | public static native void replaceNines(int[] inputArray);
64 |
65 | public static void replaceNinesJ(int[] inputArray) {
66 | for (int i = 0; i < inputArray.length; i++) {
67 | if (inputArray[i] == 9) {
68 | inputArray[i] = 0;
69 | }
70 | }
71 | }
72 |
73 | /**
74 | * Int array to string.
75 | */
76 | public static native String intArrayToString(int[] inputArray);
77 |
78 | public static String intArrayToStringJ(int[] inputArray) {
79 | StringBuilder binary = new StringBuilder();
80 | for (int num : inputArray) {
81 | binary.append(num);
82 | }
83 | return binary.toString();
84 | }
85 |
86 | /**
87 | * native method for calculating the Convolution 1D.
88 | */
89 | public static native double[] calConv1D(double[] inputArray1, double[] inputArray2);
90 |
91 | /**
92 | * get the single digit number and set it to the target one.
93 | */
94 | public static int replaceSingleDigit(int target, int singleDigit) {
95 | return (target / 10) * 10 + singleDigit;
96 | }
97 |
98 | public static int replaceSingleDigit(double target, int singleDigit) {
99 | return ((int) target / 10) * 10 + singleDigit;
100 | }
101 |
102 |
103 | /**
104 | * Get text between two strings. Passed limiting strings are not
105 | * included into result.
106 | *
107 | * @param text Text to search in.
108 | */
109 | public static String getBetweenStrings(String text, boolean isText, DetectFinishListener listener) {
110 | String result = null;
111 | if (isText) {
112 | try {
113 | result = text.substring(text.indexOf(LSB_TEXT_PREFIX_FLAG) + LSB_TEXT_SUFFIX_FLAG.length(),
114 | text.length());
115 | result = result.substring(0, result.indexOf(LSB_TEXT_SUFFIX_FLAG));
116 | } catch (StringIndexOutOfBoundsException e) {
117 | listener.onFailure(ERROR_NO_WATERMARK_FOUND);
118 | }
119 | } else {
120 | try {
121 | result = text.substring(text.indexOf(LSB_IMG_PREFIX_FLAG) + LSB_IMG_SUFFIX_FLAG.length(),
122 | text.length());
123 | result = result.substring(0, result.indexOf(LSB_IMG_SUFFIX_FLAG));
124 | } catch (StringIndexOutOfBoundsException e) {
125 | listener.onFailure(ERROR_NO_WATERMARK_FOUND);
126 | }
127 | }
128 |
129 | return result;
130 | }
131 |
132 | /**
133 | * cast an int array to a double array.
134 | * System.arrayCopy cannot cast the int array to a double one.
135 | */
136 | @SuppressWarnings("PMD")
137 | public static double[] copyFromIntArray(int[] source) {
138 | double[] dest = new double[source.length];
139 | for (int i = 0; i < source.length; i++) {
140 | dest[i] = source[i];
141 | }
142 | return dest;
143 | }
144 |
145 | /**
146 | * cast a double array to an int array.
147 | * System.arrayCopy cannot cast the double array to an int one.
148 | */
149 | @SuppressWarnings("PMD")
150 | public static int[] copyFromDoubleArray(double[] source) {
151 | int[] dest = new int[source.length];
152 | for (int i = 0; i < source.length; i++) {
153 | dest[i] = (int) source[i];
154 | }
155 | return dest;
156 | }
157 |
158 | }
159 |
--------------------------------------------------------------------------------
/androidwm/src/main/java/com/watermark/androidwm/bean/WatermarkText.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Yizheng Huang
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.watermark.androidwm.bean;
18 |
19 |
20 | import android.graphics.Color;
21 | import android.graphics.Paint;
22 | import androidx.annotation.ColorInt;
23 | import androidx.annotation.FontRes;
24 | import android.widget.EditText;
25 | import android.widget.TextView;
26 |
27 | /**
28 | * It's a wrapper of the watermark text.
29 | *
30 | * @author huangyz0918 (huangyz0918@gmail.com)
31 | * @since 29/08/2018
32 | */
33 | public class WatermarkText extends WatermarkObject
27 | * The vector can have any length. This is a wrapper function.
28 | */
29 | public static void transform(double[] real, double[] imag) {
30 | int n = real.length;
31 | if (n != imag.length) {
32 | throw new IllegalArgumentException("Mismatched lengths");
33 | }
34 | if ((n & (n - 1)) == 0) {
35 | transformRadix2(real, imag);
36 | } else {
37 | transformBlueStein(real, imag);
38 | }
39 |
40 | }
41 |
42 |
43 | /**
44 | * Computes the inverse discrete Fourier transform (IDFT) of the given
45 | * complex vector, storing the result back into the vector.
46 | *
47 | * The vector can have any length. This is a wrapper function.
48 | * This transform does not perform scaling, so the inverse is not a true inverse.
49 | */
50 | private static void inverseTransform(double[] real, double[] imag) {
51 | transform(imag, real);
52 | }
53 |
54 | /**
55 | * Computes the discrete Fourier transform (DFT) of the given complex vector,
56 | * storing the result back into the vector.
57 | *
58 | * The vector's length must be a power of 2. Uses the Cooley-Tukey
59 | * decimation-in-time radix-2 algorithm.
60 | */
61 | private static void transformRadix2(double[] real, double[] imag) {
62 | int n = real.length;
63 | if (n != imag.length) {
64 | throw new IllegalArgumentException("Mismatched lengths");
65 | }
66 |
67 | int levels = 31 - Integer.numberOfLeadingZeros(n);
68 | if (1 << levels != n) {
69 | throw new IllegalArgumentException("Length is not a power of 2");
70 | }
71 |
72 | double[] cosTable = new double[n / 2];
73 | double[] sinTable = new double[n / 2];
74 | for (int i = 0; i < n / 2; i++) {
75 | cosTable[i] = Math.cos(2 * Math.PI * i / n);
76 | sinTable[i] = Math.sin(2 * Math.PI * i / n);
77 | }
78 |
79 | for (int i = 0; i < n; i++) {
80 | int j = Integer.reverse(i) >>> (32 - levels);
81 | if (j > i) {
82 | double temp = real[i];
83 | real[i] = real[j];
84 | real[j] = temp;
85 | temp = imag[i];
86 | imag[i] = imag[j];
87 | imag[j] = temp;
88 | }
89 | }
90 |
91 | for (int size = 2; size <= n; size *= 2) {
92 | int halfSize = size / 2;
93 | int tableStep = n / size;
94 |
95 | for (int i = 0; i < n; i += size) {
96 | for (int j = i, k = 0; j < i + halfSize; j++, k += tableStep) {
97 | int l = j + halfSize;
98 | double tpre = real[l] * cosTable[k] + imag[l] * sinTable[k];
99 | double tpim = -real[l] * sinTable[k] + imag[l] * cosTable[k];
100 | real[l] = real[j] - tpre;
101 | imag[l] = imag[j] - tpim;
102 | real[j] += tpre;
103 | imag[j] += tpim;
104 | }
105 | }
106 |
107 | if (size == n) {
108 | break;
109 | }
110 |
111 | }
112 | }
113 |
114 |
115 | /**
116 | * Computes the discrete Fourier transform (DFT) of the given complex vector,
117 | * storing the result back into the vector.
118 | *
119 | * The vector can have any length. This requires the convolution function,
120 | * which in turn requires the radix-2 FFT function.
121 | *
122 | * Uses Bluestein's chirp z-transform algorithm.
123 | */
124 | private static void transformBlueStein(double[] real, double[] imag) {
125 | int n = real.length;
126 | if (n != imag.length) {
127 | throw new IllegalArgumentException("Mismatched lengths");
128 | }
129 | if (n >= 0x20000000) {
130 | throw new IllegalArgumentException("Array too large");
131 | }
132 |
133 | int m = Integer.highestOneBit(n) * 4;
134 |
135 | double[] cosTable = new double[n];
136 | double[] sinTable = new double[n];
137 | for (int i = 0; i < n; i++) {
138 | int j = (int) ((long) i * i % (n * 2));
139 | cosTable[i] = Math.cos(Math.PI * j / n);
140 | sinTable[i] = Math.sin(Math.PI * j / n);
141 | }
142 |
143 | double[] aReal = new double[m];
144 | double[] aImag = new double[m];
145 | for (int i = 0; i < n; i++) {
146 | aReal[i] = real[i] * cosTable[i] + imag[i] * sinTable[i];
147 | aImag[i] = -real[i] * sinTable[i] + imag[i] * cosTable[i];
148 | }
149 | double[] bReal = new double[m];
150 | double[] bImag = new double[m];
151 | bReal[0] = cosTable[0];
152 | bImag[0] = sinTable[0];
153 | for (int i = 1; i < n; i++) {
154 | bReal[i] = bReal[m - i] = cosTable[i];
155 | bImag[i] = bImag[m - i] = sinTable[i];
156 | }
157 |
158 | double[] cReal = new double[m];
159 | double[] cImag = new double[m];
160 | convolve(aReal, aImag, bReal, bImag, cReal, cImag);
161 |
162 | for (int i = 0; i < n; i++) {
163 | real[i] = cReal[i] * cosTable[i] + cImag[i] * sinTable[i];
164 | imag[i] = -cReal[i] * sinTable[i] + cImag[i] * cosTable[i];
165 | }
166 | }
167 |
168 | /**
169 | * Computes the circular convolution of the given complex vectors.
170 | * Each vector's length must be the same.
171 | */
172 | private static void convolve(double[] xReal, double[] xImag,
173 | double[] yReal, double[] yImag, double[] outReal, double[] outImag) {
174 |
175 | int n = xReal.length;
176 | if (n != xImag.length || n != yReal.length || n != yImag.length
177 | || n != outReal.length || n != outImag.length) {
178 | throw new IllegalArgumentException("Mismatched lengths");
179 | }
180 |
181 | xReal = xReal.clone();
182 | xImag = xImag.clone();
183 | yReal = yReal.clone();
184 | yImag = yImag.clone();
185 | transform(xReal, xImag);
186 | transform(yReal, yImag);
187 |
188 | for (int i = 0; i < n; i++) {
189 | double temp = xReal[i] * yReal[i] - xImag[i] * yImag[i];
190 | xImag[i] = xImag[i] * yReal[i] + xReal[i] * yImag[i];
191 | xReal[i] = temp;
192 | }
193 |
194 | inverseTransform(xReal, xImag);
195 |
196 | for (int i = 0; i < n; i++) {
197 | outReal[i] = xReal[i] / n;
198 | outImag[i] = xImag[i] / n;
199 | }
200 | }
201 |
202 | }
--------------------------------------------------------------------------------
/androidwm/src/main/java/com/watermark/androidwm/task/FDWatermarkTask.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Yizheng Huang
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.watermark.androidwm.task;
18 |
19 |
20 | import android.content.Context;
21 | import android.graphics.Bitmap;
22 | import android.graphics.Color;
23 | import android.os.AsyncTask;
24 |
25 | import com.watermark.androidwm.bean.WatermarkText;
26 | import com.watermark.androidwm.listener.BuildFinishListener;
27 | import com.watermark.androidwm.bean.AsyncTaskParams;
28 | import com.watermark.androidwm.utils.FastDctFft;
29 |
30 | import static com.watermark.androidwm.utils.BitmapUtils.pixel2ARGBArray;
31 | import static com.watermark.androidwm.utils.BitmapUtils.getBitmapPixels;
32 | import static com.watermark.androidwm.utils.BitmapUtils.textAsBitmap;
33 | import static com.watermark.androidwm.utils.Constant.ERROR_CREATE_FAILED;
34 | import static com.watermark.androidwm.utils.Constant.ERROR_NO_BACKGROUND;
35 | import static com.watermark.androidwm.utils.Constant.ERROR_NO_WATERMARKS;
36 | import static com.watermark.androidwm.utils.Constant.ERROR_PIXELS_NOT_ENOUGH;
37 | import static com.watermark.androidwm.utils.StringUtils.copyFromIntArray;
38 |
39 | /**
40 | * This is a tack that use Fast Fourier Transform for an image, to
41 | * build the image and text watermark into a frequency domain.
42 | *
43 | * @author huangyz0918 (huangyz0918@gmail.com)
44 | */
45 | public class FDWatermarkTask extends AsyncTask
123 | * This is the native version.
124 | */
125 | extern "C"
126 | JNIEXPORT jstring JNICALL
127 | Java_com_watermark_androidwm_utils_StringUtils_stringToBinary(JNIEnv *env, jobject instance,
128 | jstring inputText_) {
129 | const char *inputText = env->GetStringUTFChars(inputText_, 0);
130 | if (inputText == NULL) {
131 | return NULL;
132 | }
133 |
134 | string input = jstring2string(env, inputText_);
135 | string result;
136 |
137 | for (int i = 0; i < input.length(); i++) {
138 | string temp = bitset<8>(input[i]).to_string();
139 | result.append(temp);
140 | }
141 |
142 | env->ReleaseStringUTFChars(inputText_, inputText);
143 |
144 | return env->NewStringUTF(result.c_str());
145 | }
146 |
147 |
148 | /**
149 | * String to integer array.
150 | *
151 | * This is the native version.
152 | */
153 | extern "C"
154 | JNIEXPORT jintArray JNICALL
155 | Java_com_watermark_androidwm_utils_StringUtils_stringToIntArray(JNIEnv *env, jobject instance,
156 | jstring inputString_) {
157 | const char *inputString = env->GetStringUTFChars(inputString_, 0);
158 | string input = jstring2string(env, inputString_);
159 |
160 | int result[input.length()];
161 |
162 | jsize size = env->GetStringLength(inputString_);
163 | jintArray resultArray = env->NewIntArray(size);
164 |
165 | for (int i = 0; i < input.length(); ++i) {
166 | result[i] = input[i] - '0';
167 | }
168 |
169 | env->SetIntArrayRegion(resultArray, 0, size, result);
170 | env->ReleaseStringUTFChars(inputString_, inputString);
171 |
172 | return resultArray;
173 | }
174 |
175 |
176 | /**
177 | * Converting a binary string to a ASCII string.
178 | */
179 | extern "C"
180 | JNIEXPORT jstring JNICALL
181 | Java_com_watermark_androidwm_utils_StringUtils_binaryToString(JNIEnv *env, jobject instance,
182 | jstring inputText_) {
183 | const char *inputText = env->GetStringUTFChars(inputText_, 0);
184 | string inputString = jstring2string(env, inputText_);
185 |
186 | stringstream stream(inputString);
187 | string output;
188 |
189 | while (stream.good()) {
190 | bitset<8> bits;
191 | stream >> bits;
192 | char c = char(bits.to_ulong());
193 | output += c;
194 | }
195 |
196 | jstring outputString = env->NewStringUTF(output.c_str());
197 | env->ReleaseStringUTFChars(inputText_, inputText);
198 | return outputString;
199 | }
200 |
201 | /**
202 | * Replace the wrong rgb number in a form of binary,
203 | * the only case is 0 - 1 = 9, so, we need to replace
204 | * all nines to zero.
205 | */
206 | extern "C"
207 | JNIEXPORT void JNICALL
208 | Java_com_watermark_androidwm_utils_StringUtils_replaceNines(JNIEnv *env, jobject instance,
209 | jintArray inputArray_) {
210 | jint *inputArray = env->GetIntArrayElements(inputArray_, NULL);
211 | jsize size = env->GetArrayLength(inputArray_);
212 |
213 | for (int i = 0; i < size; i++) {
214 | if (inputArray[i] == 9) {
215 | inputArray[i] = 0;
216 | }
217 | }
218 |
219 | env->ReleaseIntArrayElements(inputArray_, inputArray, 0);
220 | }
221 |
222 | /**
223 | * Int array to string.
224 | */
225 | extern "C"
226 | JNIEXPORT jstring JNICALL
227 | Java_com_watermark_androidwm_utils_StringUtils_intArrayToString(JNIEnv *env, jobject instance,
228 | jintArray inputArray_) {
229 | jint *inputArray = env->GetIntArrayElements(inputArray_, NULL);
230 | jsize size = env->GetArrayLength(inputArray_);
231 | ostringstream oss("");
232 |
233 | for (int i = 0; i < size; ++i) {
234 | oss << inputArray[i];
235 | }
236 |
237 | string output = oss.str();
238 |
239 | env->ReleaseIntArrayElements(inputArray_, inputArray, 0);
240 | return env->NewStringUTF(output.c_str());
241 | }
242 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AndroidWM
2 | [](https://app.codacy.com/app/huangyz0918/AndroidWM?utm_source=github.com&utm_medium=referral&utm_content=huangyz0918/AndroidWM&utm_campaign=Badge_Grade_Dashboard)[](https://app.fossa.io/projects/git%2Bgithub.com%2Fhuangyz0918%2FAndroidWM?ref=badge_shield) [](https://github.com/huangyz0918/AndroidWM/blob/master/wikis/WIKI.md)
3 |
4 | A lightweight android image watermark library that supports encrypted watermarks. [中文版本](./README-CN.md)
5 |
6 |
7 |
8 | 
9 |
10 |
11 | # Download Library
12 |
13 | ### Gradle:
14 | For __androidWM__ supports the invisible digital watermarks (package size: 1Mb):
15 |
16 | ```gradle
17 | implementation 'com.huangyz0918:androidwm:0.2.3'
18 | ```
19 |
20 | For __androidWM-light__ only supports the visible watermarks (package size: 28Kb):
21 |
22 | ```gradle
23 | implementation 'com.huangyz0918:androidwm-light:0.1.2'
24 | ```
25 |
26 | # Quick Start
27 | ### Build a Watermark
28 | After downloading the library and adding it into your project, You can create a `WatermarkImage` or `WatermarkText` and do some pre-settings with their instance.
29 |
30 | ```java
31 | WatermarkText watermarkText = new WatermarkText(inputText)
32 | .setPositionX(0.5)
33 | .setPositionY(0.5)
34 | .setOrigin(new WatermarkPosition(0.5, 0.5))
35 | .setTextColor(Color.WHITE)
36 | .setTextFont(R.font.champagne)
37 | .setTextShadow(0.1f, 5, 5, Color.BLUE)
38 | .setTextAlpha(150)
39 | .setRotation(30)
40 | .setTextSize(20);
41 | ```
42 |
43 | There are many attributes that can help you to make a customization with a text watermark or an image watermark. You can get more information from the documentation section that follows.
44 |
45 | After the preparation is complete, you need a `WatermarkBuilder` to create a watermark image. You can get an instance from the `create` method of `WatermarkBuilder`, and, you need to put a `Bitmap` or an int `Drawable` as the background image first.
46 |
47 | ```java
48 | WatermarkBuilder
49 | .create(context, backgroundBitmap)
50 | .loadWatermarkText(watermarkText) // use .loadWatermarkImage(watermarkImage) to load an image.
51 | .getWatermark()
52 | .setToImageView(imageView);
53 | ```
54 |
55 | 
56 |
57 | ### Select the Drawing Mode
58 | You can select normal mode (default) or tile mode in `WatermarkBuilder.setTileMode()`:
59 |
60 | ```java
61 | WatermarkBuilder
62 | .create(this, backgroundBitmap)
63 | .loadWatermarkText(watermarkText)
64 | .setTileMode(true) // select different drawing mode.
65 | .getWatermark()
66 | .setToImageView(backgroundView);
67 | ```
68 |
69 | Boom! the watermark has been drawed now:
70 |
71 | 
72 |
73 | ### Get the Output
74 | You can create both text watermark and image watermark, and load them into your `WatermarkBuilder`. If you want to get the result bitmap, we also have a `.getOutputImage()` method for you after getting the watermark:
75 |
76 | ```java
77 | Bitmap bitmap = WatermarkBuilder
78 | .create(this, backgroundBitmap)
79 | .getWatermark()
80 | .getOutputImage();
81 | ```
82 |
83 | ### Build Multiple Watermarks
84 | And if you want to add many watermarks at the same time, you can use a `List<>` to hold your watermarks. You can add the `List<>` into the background image by ` .loadWatermarkTexts(watermarkTexts)`, the same as watermark images:
85 |
86 | ```java
87 | WatermarkBuilder
88 | .create(this, backgroundBitmap)
89 | .loadWatermarkTexts(watermarkTexts)
90 | .loadWatermarkImages(watermarkImages)
91 | .getWatermark();
92 | ```
93 |
94 | ### Ways of Loading Resources
95 | If you want to load a watermark image or a watermark text from a view or resources, you can use those methods:
96 |
97 | ```java
98 | WatermarkText watermarkText = new WatermarkText(editText); // for a text from EditText.
99 | WatermarkText watermarkText = new WatermarkText(textView); // for a text from TextView.
100 | WatermarkImage watermarkImage = new WatermarkImage(imageView); // for an image from ImageView.
101 | WatermarkImage watermarkImage = new WatermarkImage(this, R.drawable.image); // for an image from Resource.
102 | ```
103 |
104 | The background loaded in `WatermarkBuilder` can be created from resources or `ImageView` too:
105 |
106 | ```java
107 | WatermarkBuilder
108 | .create(this, backgroundImageView) // .create(this, R.drawable.background)
109 | .getWatermark()
110 | ```
111 |
112 | If you didn't load a watermark ,the default value is as the same as background, nothing will be changed.
113 |
114 | ### Invisible Watermarks (beta)
115 |
116 | In this library, we also support the invisible watermark and the detection of them. We can use two ways to build an invisible watermark: the LSB (spatial domain) and the wavelet transform (Frequency domain). All you need to do is to use a boolean (isLSB) to distinguish them. __(PS. the watermark in frequency domain is under developing)__
117 |
118 | You can create a new invisible watermark by the `WatermarkBuilder`'s `.setInvisibleWMListener`:
119 |
120 | ```java
121 | WatermarkBuilder
122 | .create(this, backgroundBitmap)
123 | .loadWatermarkImage(watermarkBitmap)
124 | .setInvisibleWMListener(true, new BuildFinishListener
142 | * We set the new {@link Bitmap} to a fixed width = 512 pixels.
143 | *
144 | * @return {@link Bitmap} the new bitmap.
145 | */
146 | public static Bitmap resizeBitmap(Bitmap inputBitmap, int maxImageSize) {
147 | float ratio = Math.min(
148 | (float) maxImageSize / inputBitmap.getWidth(),
149 | (float) maxImageSize / inputBitmap.getHeight());
150 | int width = Math.round(ratio * inputBitmap.getWidth());
151 | int height = Math.round(ratio * inputBitmap.getHeight());
152 |
153 | return Bitmap.createScaledBitmap(inputBitmap, width,
154 | height, true);
155 | }
156 |
157 | /**
158 | * Convert a Bitmap to a String.
159 | */
160 | public static String bitmapToString(Bitmap bitmap) {
161 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
162 | bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream);
163 | byte[] b = byteArrayOutputStream.toByteArray();
164 | return Base64.encodeToString(b, Base64.DEFAULT);
165 | }
166 |
167 | /**
168 | * Convert a String to a Bitmap.
169 | *
170 | * @return bitmap (from given string)
171 | */
172 | public static Bitmap stringToBitmap(String encodedString) {
173 | try {
174 | byte[] encodeByte = Base64.decode(encodedString, Base64.DEFAULT);
175 | return BitmapFactory.decodeByteArray(encodeByte, 0, encodeByte.length);
176 | } catch (Exception e) {
177 | Timber.e(e.toString());
178 | return null;
179 | }
180 | }
181 |
182 | /**
183 | * Saving a bitmap instance into local PNG.
184 | */
185 | public static void saveAsPNG(Bitmap inputBitmap, String filePath, boolean withTime) {
186 | String sdStatus = Environment.getExternalStorageState();
187 | if (!sdStatus.equals(Environment.MEDIA_MOUNTED)) {
188 | Timber.e("SD card is not available/writable right now.");
189 | }
190 |
191 | @SuppressLint("SimpleDateFormat") String timeStamp =
192 | new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US).format(Calendar.getInstance().getTime());
193 |
194 | FileOutputStream out = null;
195 | try {
196 | if (withTime) {
197 | out = new FileOutputStream(filePath + timeStamp + ".png");
198 | } else {
199 | out = new FileOutputStream(filePath + "watermarked" + ".png");
200 | }
201 | inputBitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
202 | // PNG is a lossless format, the compression factor (100) is ignored
203 | } catch (Exception e) {
204 | Timber.e(e.toString());
205 | } finally {
206 | try {
207 | if (out != null) {
208 | out.close();
209 | }
210 | } catch (IOException e) {
211 | Timber.e(e.toString());
212 | }
213 | }
214 | }
215 |
216 | /**
217 | * convert the background bitmap into pixel array.
218 | */
219 | public static int[] getBitmapPixels(Bitmap inputBitmap) {
220 | int[] backgroundPixels = new int[inputBitmap.getWidth() * inputBitmap.getHeight()];
221 | inputBitmap.getPixels(backgroundPixels, 0, inputBitmap.getWidth(), 0, 0,
222 | inputBitmap.getWidth(), inputBitmap.getHeight());
223 | return backgroundPixels;
224 | }
225 |
226 |
227 | /**
228 | * Bitmap to Pixels then converting it to an ARGB int array.
229 | */
230 | public static int[] pixel2ARGBArray(int[] inputPixels) {
231 | int[] bitmapArray = new int[4 * inputPixels.length];
232 | for (int i = 0; i < inputPixels.length; i++) {
233 | bitmapArray[4 * i] = Color.alpha(inputPixels[i]);
234 | bitmapArray[4 * i + 1] = Color.red(inputPixels[i]);
235 | bitmapArray[4 * i + 2] = Color.green(inputPixels[i]);
236 | bitmapArray[4 * i + 3] = Color.blue(inputPixels[i]);
237 | }
238 |
239 | return bitmapArray;
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/watermark/androidwm/sample/MainActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Yizheng Huang
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.watermark.androidwm.sample;
18 |
19 | import android.graphics.Bitmap;
20 | import android.graphics.BitmapFactory;
21 | import android.graphics.Color;
22 | import androidx.appcompat.app.AppCompatActivity;
23 | import android.os.Bundle;
24 | import android.view.View;
25 | import android.widget.Button;
26 | import android.widget.EditText;
27 | import android.widget.ImageView;
28 |
29 | import android.widget.ProgressBar;
30 | import android.widget.RadioButton;
31 | import android.widget.Toast;
32 |
33 | import com.bumptech.glide.Glide;
34 | import com.watermark.androidwm.WatermarkDetector;
35 | import com.watermark.androidwm.bean.WatermarkPosition;
36 | import com.watermark.androidwm.task.DetectionReturnValue;
37 | import com.watermark.androidwm.listener.BuildFinishListener;
38 | import com.watermark.androidwm.WatermarkBuilder;
39 | import com.watermark.androidwm.bean.WatermarkImage;
40 | import com.watermark.androidwm.bean.WatermarkText;
41 | import com.watermark.androidwm.listener.DetectFinishListener;
42 |
43 | import timber.log.Timber;
44 | //import com.watermark.androidwm.utils.BitmapUtils;
45 |
46 | /**
47 | * This is the sample for library: androidwm.
48 | *
49 | * @author huangyz0918 (huangyz0918@gmail.com)
50 | * @since 29/08/2018
51 | */
52 | public class MainActivity extends AppCompatActivity {
53 |
54 | private RadioButton mode_single;
55 | private RadioButton mode_tile;
56 | private RadioButton mode_invisible;
57 | private Button btnAddText;
58 | private Button btnAddImg;
59 | private Button btnDetectImage;
60 | private Button btnDetectText;
61 | private Button btnClear;
62 |
63 | private ImageView backgroundView;
64 | private ImageView watermarkView;
65 | private Bitmap watermarkBitmap;
66 |
67 | private EditText editText;
68 |
69 | private ProgressBar progressBar;
70 |
71 | @Override
72 | protected void onCreate(Bundle savedInstanceState) {
73 | super.onCreate(savedInstanceState);
74 | setContentView(R.layout.activity_main);
75 | initViews();
76 | initEvents();
77 | }
78 |
79 | private void initViews() {
80 | mode_single = findViewById(R.id.mode_single);
81 | mode_tile = findViewById(R.id.mode_tile);
82 | mode_invisible = findViewById(R.id.mode_invisible);
83 | btnAddImg = findViewById(R.id.btn_add_image);
84 | btnAddText = findViewById(R.id.btn_add_text);
85 | btnDetectImage = findViewById(R.id.btn_detect_image);
86 | btnDetectText = findViewById(R.id.btn_detect_text);
87 | btnClear = findViewById(R.id.btn_clear_watermark);
88 |
89 | editText = findViewById(R.id.editText);
90 | backgroundView = findViewById(R.id.imageView);
91 | watermarkView = findViewById(R.id.imageView_watermark);
92 |
93 | progressBar = findViewById(R.id.progressBar);
94 |
95 | watermarkBitmap = BitmapFactory.decodeResource(getResources(),
96 | R.drawable.test_watermark);
97 |
98 | watermarkView.setVisibility(View.GONE);
99 | }
100 |
101 | private void initEvents() {
102 | // The sample method of adding a text watermark.
103 | btnAddText.setOnClickListener((View v) -> {
104 | String markText = editText.getText().toString();
105 | if(markText.trim().isEmpty()){
106 | Toast.makeText(this, "Please Input Text First", Toast.LENGTH_SHORT).show();
107 | return;
108 | }
109 | if(mode_invisible.isChecked()){
110 | createInvisibleTextMark();
111 | return;
112 | }
113 | WatermarkText watermarkText = new WatermarkText(markText)
114 | .setPosition(new WatermarkPosition(0.5, 0.5))
115 | .setOrigin(new WatermarkPosition(0.5, 0.5))
116 | .setTextSize(40)
117 | .setTextAlpha(255)
118 | .setTextColor(Color.WHITE)
119 | .setTextFont(R.font.champagne)
120 | .setTextShadow(0.1f, 5, 5, Color.BLUE);
121 |
122 | WatermarkBuilder.create(this, backgroundView)
123 | .setTileMode(mode_tile.isChecked())
124 | .loadWatermarkText(watermarkText)
125 | .getWatermark()
126 | .setToImageView(backgroundView);
127 | });
128 |
129 | // The sample method of adding an image watermark.
130 | btnAddImg.setOnClickListener((View v) -> {
131 | if(mode_invisible.isChecked()){
132 | createInvisibleImgMark();
133 | return;
134 | }
135 | // Math.random()
136 | WatermarkImage watermarkImage = new WatermarkImage(watermarkBitmap)
137 | .setImageAlpha(80)
138 | .setPositionX(Math.random())
139 | .setPositionY(Math.random())
140 | // .setPosition(new WatermarkPosition(0.5, 0.5))
141 | .setOrigin(new WatermarkPosition(0.5, 0.5))
142 | .setRotation(15)
143 | .setSize(0.1);
144 |
145 | WatermarkBuilder
146 | .create(this, backgroundView)
147 | .loadWatermarkImage(watermarkImage)
148 | .setTileMode(mode_tile.isChecked())
149 | .getWatermark()
150 | .setToImageView(backgroundView);
151 |
152 | });
153 |
154 | // detect the text watermark.
155 | btnDetectText.setOnClickListener((View v) -> {
156 | progressBar.setVisibility(View.VISIBLE);
157 | WatermarkDetector.create(backgroundView, true)
158 | .detect(new DetectFinishListener() {
159 | @Override
160 | public void onSuccess(DetectionReturnValue returnValue) {
161 | progressBar.setVisibility(View.GONE);
162 | if (returnValue != null) {
163 | Toast.makeText(MainActivity.this, "Successfully detected invisible text: "
164 | + returnValue.getWatermarkString(), Toast.LENGTH_SHORT).show();
165 | }
166 | }
167 |
168 | @Override
169 | public void onFailure(String message) {
170 | progressBar.setVisibility(View.GONE);
171 | Timber.e(message);
172 | }
173 | });
174 | });
175 |
176 | // detect the image watermark.
177 | btnDetectImage.setOnClickListener((View v) -> {
178 | progressBar.setVisibility(View.VISIBLE);
179 | WatermarkDetector.create(backgroundView, true)
180 | .detect(new DetectFinishListener() {
181 | @Override
182 | public void onSuccess(DetectionReturnValue returnValue) {
183 | progressBar.setVisibility(View.GONE);
184 | Toast.makeText(MainActivity.this,
185 | "Successfully detected invisible watermark!", Toast.LENGTH_SHORT).show();
186 | if (returnValue != null) {
187 | watermarkView.setVisibility(View.VISIBLE);
188 | watermarkView.setImageBitmap(returnValue.getWatermarkBitmap());
189 | }
190 | }
191 |
192 | @Override
193 | public void onFailure(String message) {
194 | progressBar.setVisibility(View.GONE);
195 | Timber.e(message);
196 | }
197 | });
198 | });
199 |
200 | // reload the background.
201 | btnClear.setOnClickListener((View v) -> {
202 | Glide.with(this).load(R.drawable.test2)
203 | .into(backgroundView);
204 | watermarkView.setVisibility(View.GONE);
205 | });
206 |
207 | }
208 |
209 | private void createInvisibleImgMark(){
210 | progressBar.setVisibility(View.VISIBLE);
211 | WatermarkBuilder
212 | .create(this, backgroundView)
213 | .loadWatermarkImage(watermarkBitmap)
214 | .setInvisibleWMListener(true, new BuildFinishListener