From 0cd38991365bd4900b0214e66b234d5f737421fe Mon Sep 17 00:00:00 2001 From: Oleksandr Kucherenko Date: Fri, 12 Oct 2018 16:03:56 +0200 Subject: [PATCH 01/11] more cleanup/ignore rules --- .gitignore | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index b6d14825..19e61b3a 100644 --- a/.gitignore +++ b/.gitignore @@ -29,11 +29,18 @@ npm-debug.log # android # -android/build/ -android/.gradle/ -android/.idea/ -android/*.iml -android/gradle/ +.vscode/ +.settings/ +android/bin +android/gradle/wrapper android/gradlew android/gradlew.bat android/local.properties +*.iml +.gradle +/local.properties +.idea/ +captures/ +.externalNativeBuild +.project + From de310f1b3d789d03cc00f6fc4fae8b8988fba043 Mon Sep 17 00:00:00 2001 From: Oleksandr Kucherenko Date: Fri, 12 Oct 2018 16:24:47 +0200 Subject: [PATCH 02/11] added support of RAW image format and zip-base64 packaging of the results --- .../reactnativeviewshot/RNViewShotModule.java | 136 ++++++++++-------- 1 file changed, 77 insertions(+), 59 deletions(-) diff --git a/android/src/main/java/fr/greweb/reactnativeviewshot/RNViewShotModule.java b/android/src/main/java/fr/greweb/reactnativeviewshot/RNViewShotModule.java index b5afdaad..a2e1c8a2 100644 --- a/android/src/main/java/fr/greweb/reactnativeviewshot/RNViewShotModule.java +++ b/android/src/main/java/fr/greweb/reactnativeviewshot/RNViewShotModule.java @@ -1,35 +1,36 @@ package fr.greweb.reactnativeviewshot; +import android.app.Activity; import android.content.Context; -import android.graphics.Bitmap; import android.net.Uri; import android.os.AsyncTask; -import android.os.Environment; +import android.support.annotation.NonNull; import android.util.DisplayMetrics; -import android.view.View; - -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; +import android.util.Log; import com.facebook.react.bridge.GuardedAsyncTask; -import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.uimanager.UIBlock; import com.facebook.react.uimanager.UIManagerModule; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.util.Collections; -import java.util.HashMap; import java.util.Map; +import fr.greweb.reactnativeviewshot.ViewShot.Formats; +import fr.greweb.reactnativeviewshot.ViewShot.Results; + public class RNViewShotModule extends ReactContextBaseJavaModule { + public static final String RNVIEW_SHOT = "RNViewShot"; + private final ReactApplicationContext reactContext; public RNViewShotModule(ReactApplicationContext reactContext) { @@ -39,7 +40,7 @@ public RNViewShotModule(ReactApplicationContext reactContext) { @Override public String getName() { - return "RNViewShot"; + return RNVIEW_SHOT; } @Override @@ -67,30 +68,40 @@ public void releaseCapture(String uri) { @ReactMethod public void captureRef(int tag, ReadableMap options, Promise promise) { - ReactApplicationContext context = getReactApplicationContext(); - String format = options.getString("format"); - Bitmap.CompressFormat compressFormat = - format.equals("jpg") - ? Bitmap.CompressFormat.JPEG - : format.equals("webm") - ? Bitmap.CompressFormat.WEBP - : Bitmap.CompressFormat.PNG; - double quality = options.getDouble("quality"); - DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); - Integer width = options.hasKey("width") ? (int)(displayMetrics.density * options.getDouble("width")) : null; - Integer height = options.hasKey("height") ? (int)(displayMetrics.density * options.getDouble("height")) : null; - String result = options.getString("result"); - Boolean snapshotContentContainer = options.getBoolean("snapshotContentContainer"); + final ReactApplicationContext context = getReactApplicationContext(); + final DisplayMetrics dm = context.getResources().getDisplayMetrics(); + + final String extension = options.getString("format"); + final int imageFormat = "jpg".equals(extension) + ? Formats.JPEG + : "webm".equals(extension) + ? Formats.WEBP + : "raw".equals(extension) + ? Formats.RAW + : Formats.PNG; + + final double quality = options.getDouble("quality"); + final Integer scaleWidth = options.hasKey("width") ? (int) (dm.density * options.getDouble("width")) : null; + final Integer scaleHeight = options.hasKey("height") ? (int) (dm.density * options.getDouble("height")) : null; + final String resultStreamFormat = options.getString("result"); + final Boolean snapshotContentContainer = options.getBoolean("snapshotContentContainer"); + try { - File file = null; - if ("tmpfile".equals(result)) { - file = createTempFile(getReactApplicationContext(), format); + File outputFile = null; + if (Results.TEMP_FILE.equals(resultStreamFormat)) { + outputFile = createTempFile(getReactApplicationContext(), extension); } - UIManagerModule uiManager = this.reactContext.getNativeModule(UIManagerModule.class); - uiManager.addUIBlock(new ViewShot(tag, format, compressFormat, quality, width, height, file, result, snapshotContentContainer,reactContext, getCurrentActivity(), promise)); - } - catch (Exception e) { - promise.reject(ViewShot.ERROR_UNABLE_TO_SNAPSHOT, "Failed to snapshot view tag "+tag); + + final Activity activity = getCurrentActivity(); + final UIManagerModule uiManager = this.reactContext.getNativeModule(UIManagerModule.class); + + uiManager.addUIBlock(new ViewShot( + tag, extension, imageFormat, quality, + scaleWidth, scaleHeight, outputFile, resultStreamFormat, + snapshotContentContainer, reactContext, activity, promise) + ); + } catch (final Throwable ignored) { + promise.reject(ViewShot.ERROR_UNABLE_TO_SNAPSHOT, "Failed to snapshot view tag " + tag); } } @@ -106,34 +117,41 @@ public void captureScreen(ReadableMap options, Promise promise) { * image files. This is run when the catalyst instance is being destroyed (i.e. app is shutting * down) and when the module is instantiated, to handle the case where the app crashed. */ - private static class CleanTask extends GuardedAsyncTask { - private final Context mContext; + private static class CleanTask extends GuardedAsyncTask implements FilenameFilter { + private final File cacheDir; + private final File externalCacheDir; private CleanTask(ReactContext context) { super(context); - mContext = context; + + cacheDir = context.getCacheDir(); + externalCacheDir = context.getExternalCacheDir(); } @Override protected void doInBackgroundGuarded(Void... params) { - cleanDirectory(mContext.getCacheDir()); - File externalCacheDir = mContext.getExternalCacheDir(); + if (null != cacheDir) { + cleanDirectory(cacheDir); + } + if (externalCacheDir != null) { cleanDirectory(externalCacheDir); } } - private void cleanDirectory(File directory) { - File[] toDelete = directory.listFiles( - new FilenameFilter() { - @Override - public boolean accept(File dir, String filename) { - return filename.startsWith(TEMP_FILE_PREFIX); - } - }); + @Override + public final boolean accept(File dir, String filename) { + return filename.startsWith(TEMP_FILE_PREFIX); + } + + private void cleanDirectory(@NonNull final File directory) { + final File[] toDelete = directory.listFiles(this); + if (toDelete != null) { - for (File file: toDelete) { - file.delete(); + for (File file : toDelete) { + if (file.delete()) { + Log.d(RNVIEW_SHOT, "deleted file: " + file.getAbsolutePath()); + } } } } @@ -143,26 +161,26 @@ public boolean accept(File dir, String filename) { * Create a temporary file in the cache directory on either internal or external storage, * whichever is available and has more free space. */ - private File createTempFile(Context context, String ext) - throws IOException { - File externalCacheDir = context.getExternalCacheDir(); - File internalCacheDir = context.getCacheDir(); - File cacheDir; + @NonNull + private File createTempFile(@NonNull final Context context, @NonNull final String ext) throws IOException { + final File externalCacheDir = context.getExternalCacheDir(); + final File internalCacheDir = context.getCacheDir(); + final File cacheDir; + if (externalCacheDir == null && internalCacheDir == null) { throw new IOException("No cache directory available"); } + if (externalCacheDir == null) { cacheDir = internalCacheDir; - } - else if (internalCacheDir == null) { + } else if (internalCacheDir == null) { cacheDir = externalCacheDir; } else { cacheDir = externalCacheDir.getFreeSpace() > internalCacheDir.getFreeSpace() ? externalCacheDir : internalCacheDir; } - String suffix = "." + ext; - File tmpFile = File.createTempFile(TEMP_FILE_PREFIX, suffix, cacheDir); - return tmpFile; - } + final String suffix = "." + ext; + return File.createTempFile(TEMP_FILE_PREFIX, suffix, cacheDir); + } } From f8af2431894b714a385e1998a2f65397fe651579 Mon Sep 17 00:00:00 2001 From: Oleksandr Kucherenko Date: Fri, 12 Oct 2018 16:28:10 +0200 Subject: [PATCH 03/11] introduced RAW image format and ZIP-base64 result packaging algorithm --- src/index.d.ts | 7 ++++--- src/index.js | 14 ++++++++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/index.d.ts b/src/index.d.ts index ac73bd39..a79eee03 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -23,9 +23,9 @@ declare module 'react-native-view-shot' { */ height?: number; /** - * either png or jpg or webm (Android). Defaults to png. + * either png or jpg or webm (Android). Defaults to png. raw is a ARGB array of image pixels. */ - format?: 'jpg' | 'png' | 'webm'; + format?: 'jpg' | 'png' | 'webm' | 'raw'; /** * the quality. 0.0 - 1.0 (default). (only available on lossy formats like jpg) */ @@ -36,8 +36,9 @@ declare module 'react-native-view-shot' { " - base64": encode as base64 and returns the raw string. Use only with small images as this may result of * lags (the string is sent over the bridge). N.B. This is not a data uri, use data-uri instead. " - data-uri": same as base64 but also includes the Data URI scheme header. + " - zip-base64: compress data with zip deflate algorithm and than convert to base64 and return as a raw string." */ - result?: 'tmpfile' | 'base64' | 'data-uri'; + result?: 'tmpfile' | 'base64' | 'data-uri' | 'zip-base64'; /** * if true and when view is a ScrollView, the "content container" height will be evaluated instead of the * container height. diff --git a/src/index.js b/src/index.js index 756e96e1..113d8e58 100644 --- a/src/index.js +++ b/src/index.js @@ -8,9 +8,9 @@ const neverEndingPromise = new Promise(() => {}); type Options = { width?: number, height?: number, - format: "png" | "jpg" | "webm", + format: "png" | "jpg" | "webm" | "raw", quality: number, - result: "tmpfile" | "base64" | "data-uri", + result: "tmpfile" | "base64" | "data-uri" | "zip-base64", snapshotContentContainer: boolean }; @@ -21,10 +21,12 @@ if (!RNViewShot) { } const acceptedFormats = ["png", "jpg"].concat( - Platform.OS === "android" ? ["webm"] : [] + Platform.OS === "android" ? ["webm", "raw"] : [] ); -const acceptedResults = ["tmpfile", "base64", "data-uri"]; +const acceptedResults = ["tmpfile", "base64", "data-uri"].concat( + Platform.OS === "android" ? ["zip-base64"] : [] +); const defaultOptions = { format: "png", @@ -70,13 +72,13 @@ function validateOptions( if (acceptedFormats.indexOf(options.format) === -1) { options.format = defaultOptions.format; errors.push( - "option format is not in valid formats: " + acceptedFormats.join(" | ") + "option format '" + options.format + "' is not in valid formats: " + acceptedFormats.join(" | ") ); } if (acceptedResults.indexOf(options.result) === -1) { options.result = defaultOptions.result; errors.push( - "option result is not in valid formats: " + acceptedResults.join(" | ") + "option result '" + options.result + "' is not in valid formats: " + acceptedResults.join(" | ") ); } return { options, errors }; From 7b13ab7082bd33ba94a61e2a0007c022d6dd8b7f Mon Sep 17 00:00:00 2001 From: Oleksandr Kucherenko Date: Fri, 12 Oct 2018 16:28:56 +0200 Subject: [PATCH 04/11] version up for android tools and libs, sdk api --- example/android/app/build.gradle | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 1b5a957b..d2bfc100 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -65,6 +65,10 @@ import com.android.build.OutputFile * ] */ +if (!project.file("../../node_modules/react-native/react.gradle").exists()) { + throw new RuntimeException("React modules are not installed. execute: 'yarn install' in 'example' folder. ") +} + apply from: "../../node_modules/react-native/react.gradle" /** @@ -83,15 +87,17 @@ def enableSeparateBuildPerCPUArchitecture = false def enableProguardInReleaseBuilds = false android { - compileSdkVersion 23 - buildToolsVersion '25.0.3' + compileSdkVersion 27 + buildToolsVersion '28.0.3' defaultConfig { applicationId "com.viewshotexample" minSdkVersion 16 - targetSdkVersion 22 + targetSdkVersion 27 + versionCode 1 versionName "1.0" + ndk { abiFilters "armeabi-v7a", "x86" } @@ -115,7 +121,7 @@ android { variant.outputs.each { output -> // For each separate APK per architecture, set a unique version code as described here: // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits - def versionCodes = ["armeabi-v7a":1, "x86":2] + def versionCodes = ["armeabi-v7a": 1, "x86": 2] def abi = output.getFilter(OutputFile.ABI) if (abi != null) { // null for the universal-debug, universal-release variants output.versionCodeOverride = @@ -125,11 +131,23 @@ android { } } +repositories { + google() + jcenter() + mavenLocal() + maven { + // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm + url "${project.projectDir}/../node_modules/react-native/android" + } +} + dependencies { compile project(':react-native-svg') compile fileTree(dir: "libs", include: ["*.jar"]) - compile "com.android.support:appcompat-v7:23.0.1" + + compile "com.android.support:appcompat-v7:27.+" compile "com.facebook.react:react-native:+" // From node_modules + compile project(':react-native-view-shot') compile project(':gl-react-native') compile project(':react-native-maps') From a999019bc248095313399cee5dcda1bdaeeb2f41 Mon Sep 17 00:00:00 2001 From: Oleksandr Kucherenko Date: Fri, 12 Oct 2018 16:30:28 +0200 Subject: [PATCH 05/11] implemented super fast way of screenshot making. removed places that slow down screenshot making and delivery to the react side. --- .../greweb/reactnativeviewshot/ViewShot.java | 460 +++++++++++++++--- 1 file changed, 381 insertions(+), 79 deletions(-) diff --git a/android/src/main/java/fr/greweb/reactnativeviewshot/ViewShot.java b/android/src/main/java/fr/greweb/reactnativeviewshot/ViewShot.java index 33b4bdab..313fb7b1 100644 --- a/android/src/main/java/fr/greweb/reactnativeviewshot/ViewShot.java +++ b/android/src/main/java/fr/greweb/reactnativeviewshot/ViewShot.java @@ -1,12 +1,15 @@ package fr.greweb.reactnativeviewshot; -import javax.annotation.Nullable; - import android.app.Activity; -import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Point; +import android.graphics.Rect; import android.net.Uri; +import android.support.annotation.IntDef; +import android.support.annotation.NonNull; +import android.support.annotation.StringDef; import android.util.Base64; import android.view.TextureView; import android.view.View; @@ -23,42 +26,113 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.zip.Deflater; + +import javax.annotation.Nullable; /** * Snapshot utility class allow to screenshot a view. */ public class ViewShot implements UIBlock { - + //region Constants static final String ERROR_UNABLE_TO_SNAPSHOT = "E_UNABLE_TO_SNAPSHOT"; + /** + * pre-allocated output stream size for screenshot. In real life example it will eb around 7Mb. + */ + private static final int PREALLOCATE_SIZE = 64 * 1024; + /** + * ARGB size in bytes. + */ + private static final int ARGB_SIZE = 4; + + @SuppressWarnings("WeakerAccess") + @IntDef({Formats.JPEG, Formats.PNG, Formats.WEBP, Formats.RAW}) + public @interface Formats { + int JPEG = 0; // Bitmap.CompressFormat.JPEG.ordinal(); + int PNG = 1; // Bitmap.CompressFormat.PNG.ordinal(); + int WEBP = 2; // Bitmap.CompressFormat.WEBP.ordinal(); + int RAW = -1; + + Bitmap.CompressFormat[] mapping = { + Bitmap.CompressFormat.JPEG, + Bitmap.CompressFormat.PNG, + Bitmap.CompressFormat.WEBP + }; + } + + /** + * Supported Output results. + */ + @StringDef({Results.BASE_64, Results.DATA_URI, Results.TEMP_FILE, Results.ZIP_BASE_64}) + public @interface Results { + /** + * Save screenshot as temp file on device. + */ + String TEMP_FILE = "tmpfile"; + /** + * Base 64 encoded image. + */ + String BASE_64 = "base64"; + /** + * Zipped RAW image in base 64 encoding. + */ + String ZIP_BASE_64 = "zip-base64"; + /** + * Base64 data uri. + */ + String DATA_URI = "data-uri"; + } + //endregion + //region Static members + /** + * Image output buffer used as a source for base64 encoding + */ + private static byte[] outputBuffer = new byte[PREALLOCATE_SIZE]; + //endregion + + //region Class members private final int tag; private final String extension; - private final Bitmap.CompressFormat format; + @Formats + private final int format; private final double quality; private final Integer width; private final Integer height; private final File output; + @Results private final String result; private final Promise promise; private final Boolean snapshotContentContainer; + @SuppressWarnings({"unused", "FieldCanBeLocal"}) private final ReactApplicationContext reactContext; private final Activity currentActivity; + //endregion + //region Constructors + @SuppressWarnings("WeakerAccess") public ViewShot( - int tag, - String extension, - Bitmap.CompressFormat format, - double quality, + final int tag, + final String extension, + @Formats final int format, + final double quality, @Nullable Integer width, @Nullable Integer height, - File output, - String result, - Boolean snapshotContentContainer, - ReactApplicationContext reactContext, - Activity currentActivity, - Promise promise) { + final File output, + @Results final String result, + final Boolean snapshotContentContainer, + final ReactApplicationContext reactContext, + final Activity currentActivity, + final Promise promise) { this.tag = tag; this.extension = extension; this.format = format; @@ -72,7 +146,9 @@ public ViewShot( this.currentActivity = currentActivity; this.promise = promise; } + //endregion + //region Overrides @Override public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) { final View view; @@ -84,74 +160,146 @@ public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) { } if (view == null) { - promise.reject(ERROR_UNABLE_TO_SNAPSHOT, "No view found with reactTag: "+tag); + promise.reject(ERROR_UNABLE_TO_SNAPSHOT, "No view found with reactTag: " + tag); return; } + try { - if ("tmpfile".equals(result)) { - captureView(view, new FileOutputStream(output)); - final String uri = Uri.fromFile(output).toString(); - promise.resolve(uri); - } else if ("base64".equals(result)) { - final ByteArrayOutputStream os = new ByteArrayOutputStream(); - captureView(view, os); - final byte[] bytes = os.toByteArray(); - final String data = Base64.encodeToString(bytes, Base64.NO_WRAP); - promise.resolve(data); - } else if ("data-uri".equals(result)) { - final ByteArrayOutputStream os = new ByteArrayOutputStream(); - captureView(view, os); - final byte[] bytes = os.toByteArray(); - String data = Base64.encodeToString(bytes, Base64.NO_WRAP); - // correct the extension if JPG - String imageFormat = extension; - if ("jpg".equals(extension)) { - imageFormat = "jpeg"; - } - data = "data:image/"+imageFormat+";base64," + data; - promise.resolve(data); + final ReusableByteArrayOutputStream stream = new ReusableByteArrayOutputStream(outputBuffer); + stream.setSize(proposeSize(view)); + outputBuffer = stream.innerBuffer(); + + if (Results.TEMP_FILE.equals(result) && Formats.RAW == this.format) { + saveToRawFileOnDevice(view); + } else if (Results.TEMP_FILE.equals(result) && Formats.RAW != this.format) { + saveToTempFileOnDevice(view); + } else if (Results.BASE_64.equals(result) || Results.ZIP_BASE_64.equals(result)) { + saveToBase64String(view); + } else if (Results.DATA_URI.equals(result)) { + saveToDataUriString(view); } - } catch (Exception e) { - e.printStackTrace(); + } catch (final Throwable ignored) { promise.reject(ERROR_UNABLE_TO_SNAPSHOT, "Failed to capture view snapshot"); } } + //endregion + + //region Implementation + private void saveToTempFileOnDevice(@NonNull final View view) throws IOException { + final FileOutputStream fos = new FileOutputStream(output); + captureView(view, fos); + + promise.resolve(Uri.fromFile(output).toString()); + } + + private void saveToRawFileOnDevice(@NonNull final View view) throws IOException { + final String uri = Uri.fromFile(output).toString(); + + final FileOutputStream fos = new FileOutputStream(output); + final ReusableByteArrayOutputStream os = new ReusableByteArrayOutputStream(outputBuffer); + final Point size = captureView(view, os); + + // in case of buffer grow that will be a new array with bigger size + outputBuffer = os.innerBuffer(); + final int length = os.size(); + final String resolution = String.format(Locale.US, "%d:%d|", size.x, size.y); + + fos.write(resolution.getBytes(Charset.forName("US-ASCII"))); + fos.write(outputBuffer, 0, length); + fos.close(); + + promise.resolve(uri); + } - private List getAllChildren(View v) { + private void saveToDataUriString(@NonNull final View view) throws IOException { + final ReusableByteArrayOutputStream os = new ReusableByteArrayOutputStream(outputBuffer); + captureView(view, os); + outputBuffer = os.innerBuffer(); + final int length = os.size(); + + final String data = Base64.encodeToString(outputBuffer, 0, length, Base64.NO_WRAP); + + // correct the extension if JPG + final String imageFormat = "jpg".equals(extension) ? "jpeg" : extension; + + promise.resolve("data:image/" + imageFormat + ";base64," + data); + } + + private void saveToBase64String(@NonNull final View view) throws IOException { + final boolean isRaw = Formats.RAW == this.format; + final boolean isZippedBase64 = Results.ZIP_BASE_64.equals(this.result); + + final ReusableByteArrayOutputStream os = new ReusableByteArrayOutputStream(outputBuffer); + final Point size = captureView(view, os); + + // in case of buffer grow that will be a new array with bigger size + outputBuffer = os.innerBuffer(); + final int length = os.size(); + final String resolution = String.format(Locale.US, "%d:%d|", size.x, size.y); + final String header = (isRaw ? resolution : ""); + final String data; + + if (isZippedBase64) { + final Deflater deflater = new Deflater(); + deflater.setInput(outputBuffer, 0, length); + deflater.finish(); + + final ReusableByteArrayOutputStream zipped = new ReusableByteArrayOutputStream(new byte[32]); + byte[] buffer = new byte[1024]; + while (!deflater.finished()) { + int count = deflater.deflate(buffer); // returns the generated code... index + zipped.write(buffer, 0, count); + } + + data = header + Base64.encodeToString(zipped.innerBuffer(), 0, zipped.size(), Base64.NO_WRAP); + } else { + data = header + Base64.encodeToString(outputBuffer, 0, length, Base64.NO_WRAP); + } + + promise.resolve(data); + } + + @NonNull + private List getAllChildren(@NonNull final View v) { if (!(v instanceof ViewGroup)) { - ArrayList viewArrayList = new ArrayList(); + final ArrayList viewArrayList = new ArrayList<>(); viewArrayList.add(v); + return viewArrayList; } - ArrayList result = new ArrayList(); + final ArrayList result = new ArrayList<>(); ViewGroup viewGroup = (ViewGroup) v; for (int i = 0; i < viewGroup.getChildCount(); i++) { - View child = viewGroup.getChildAt(i); //Do not add any parents, just add child elements result.addAll(getAllChildren(child)); } + return result; } /** - * Screenshot a view and return the captured bitmap. - * @param view the view to capture - * @return the screenshot or null if it failed. + * Wrap {@link #captureViewImpl(View, OutputStream)} call and on end close output stream. */ - private void captureView(View view, OutputStream os) throws IOException { + private Point captureView(@NonNull final View view, @NonNull final OutputStream os) throws IOException { try { - captureViewImpl(view, os); + return captureViewImpl(view, os); } finally { os.close(); } } - private void captureViewImpl(View view, OutputStream os) { + /** + * Screenshot a view and return the captured bitmap. + * + * @param view the view to capture + * @return screenshot resolution, Width * Height + */ + private Point captureViewImpl(@NonNull final View view, @NonNull final OutputStream os) { int w = view.getWidth(); int h = view.getHeight(); @@ -161,47 +309,201 @@ private void captureViewImpl(View view, OutputStream os) { //evaluate real height if (snapshotContentContainer) { - h=0; - ScrollView scrollView = (ScrollView)view; + h = 0; + ScrollView scrollView = (ScrollView) view; for (int i = 0; i < scrollView.getChildCount(); i++) { h += scrollView.getChildAt(i).getHeight(); } } - Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); - Bitmap childBitmapBuffer; - Canvas c = new Canvas(bitmap); + + final Point resolution = new Point(w, h); + Bitmap bitmap = getBitmapForScreenshot(w, h); + + final Canvas c = new Canvas(bitmap); view.draw(c); //after view is drawn, go through children - List childrenList = getAllChildren(view); - - for (View child : childrenList) { - if(child instanceof TextureView) { - ((TextureView) child).setOpaque(false); - childBitmapBuffer = ((TextureView) child).getBitmap(child.getWidth(), child.getHeight()); - if (childBitmapBuffer != null) { - int left = child.getLeft(); - int top = child.getTop(); - View parentElem = (View)child.getParent(); - while (parentElem != null) { - if (parentElem == view) { - break; - } - left += parentElem.getLeft(); - top += parentElem.getTop(); - parentElem = (View)parentElem.getParent(); - } - c.drawBitmap(childBitmapBuffer, left + child.getPaddingLeft(), top + child.getPaddingTop(), null); + final List childrenList = getAllChildren(view); + + for (final View child : childrenList) { + // skip any child that we don't know how to process + if (!(child instanceof TextureView)) continue; + + final TextureView tvChild = (TextureView) child; + tvChild.setOpaque(false); + + final Point offsets = getParentOffsets(view, child); + final int left = child.getLeft() + child.getPaddingLeft() + offsets.x; + final int top = child.getTop() + child.getPaddingTop() + offsets.y; + final int childWidth = child.getWidth(); + final int childHeight = child.getHeight(); + final Rect source = new Rect(0, 0, childWidth, childHeight); + final Rect destination = new Rect(left, top, left + childWidth, top + childHeight); + + // get re-usable bitmap + final Bitmap childBitmapBuffer = tvChild.getBitmap(getBitmapForScreenshot(child.getWidth(), child.getHeight())); + // due to re-use of bitmaps for screenshot, we can get bitmap that is bigger in size than requested + c.drawBitmap(childBitmapBuffer, source, destination, null); + recycleBitmap(childBitmapBuffer); + } + + if (width != null && height != null && (width != w || height != h)) { + final Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, width, height, true); + recycleBitmap(bitmap); + + bitmap = scaledBitmap; + } + + // special case, just save RAW ARGB array without any compression + if (Formats.RAW == this.format && os instanceof ReusableByteArrayOutputStream) { + final int total = w * h * ARGB_SIZE; + final ReusableByteArrayOutputStream rbaos = cast(os); + bitmap.copyPixelsToBuffer(rbaos.asBuffer(total)); + rbaos.setSize(total); + } else { + final Bitmap.CompressFormat cf = Formats.mapping[this.format]; + + bitmap.compress(cf, (int) (100.0 * quality), os); + } + + recycleBitmap(bitmap); + + return resolution; // return image width and height + } + + @NonNull + private Point getParentOffsets(@NonNull final View view, @NonNull final View child) { + int left = 0; + int top = 0; + + View parentElem = (View) child.getParent(); + while (parentElem != null) { + if (parentElem == view) break; + + left += parentElem.getLeft(); + top += parentElem.getTop(); + parentElem = (View) parentElem.getParent(); + } + + return new Point(left, top); + } + + @SuppressWarnings("unchecked") + private static T cast(final A instance) { + return (T) instance; + } + //endregion + + //region Cache re-usable bitmaps + /** + * Synchronization guard. + */ + private static final Object guardBitmaps = new Object(); + /** + * Reusable bitmaps for screenshots. + */ + private static final Set weakBitmaps = Collections.newSetFromMap(new WeakHashMap()); + + /** + * Propose allocation size of the array output stream. + */ + private static int proposeSize(@NonNull final View view) { + final int w = view.getWidth(); + final int h = view.getHeight(); + + return Math.min(w * h * ARGB_SIZE, 32); + } + + /** + * Return bitmap to set of available. + */ + private static void recycleBitmap(@NonNull final Bitmap bitmap) { + synchronized (guardBitmaps) { + weakBitmaps.add(bitmap); + } + } + + /** + * Try to find a bitmap for screenshot in reusabel set and if not found create a new one. + */ + @NonNull + private static Bitmap getBitmapForScreenshot(final int width, final int height) { + synchronized (guardBitmaps) { + for (final Bitmap bmp : weakBitmaps) { + if (bmp.getWidth() * bmp.getHeight() <= width * height) { + weakBitmaps.remove(bmp); + bmp.eraseColor(Color.TRANSPARENT); + return bmp; } } } - if (width != null && height != null && (width != w || height != h)) { - bitmap = Bitmap.createScaledBitmap(bitmap, width, height, true); + return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + } + //endregion + + //region Nested declarations + + /** + * Stream that can re-use pre-allocated buffer. + */ + @SuppressWarnings("WeakerAccess") + public static class ReusableByteArrayOutputStream extends ByteArrayOutputStream { + private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + + public ReusableByteArrayOutputStream(@NonNull final byte[] buffer) { + super(0); + + this.buf = buffer; + } + + /** + * Get access to inner buffer without any memory copy operations. + */ + public byte[] innerBuffer() { + return this.buf; + } + + @NonNull + public ByteBuffer asBuffer(final int size) { + if (this.buf.length < size) { + grow(size); + } + + return ByteBuffer.wrap(this.buf); } - if (bitmap == null) { - throw new RuntimeException("Impossible to snapshot the view"); + + public void setSize(final int size) { + this.count = size; } - bitmap.compress(format, (int)(100.0 * quality), os); + + /** + * Increases the capacity to ensure that it can hold at least the + * number of elements specified by the minimum capacity argument. + * + * @param minCapacity the desired minimum capacity + */ + protected void grow(int minCapacity) { + // overflow-conscious code + int oldCapacity = buf.length; + int newCapacity = oldCapacity << 1; + if (newCapacity - minCapacity < 0) + newCapacity = minCapacity; + if (newCapacity - MAX_ARRAY_SIZE > 0) + newCapacity = hugeCapacity(minCapacity); + buf = Arrays.copyOf(buf, newCapacity); + } + + protected static int hugeCapacity(int minCapacity) { + if (minCapacity < 0) // overflow + throw new OutOfMemoryError(); + + return (minCapacity > MAX_ARRAY_SIZE) ? + Integer.MAX_VALUE : + MAX_ARRAY_SIZE; + } + } + //endregion + } From e6c59ca1b69874d576b07d0e795f61b1c732fa6f Mon Sep 17 00:00:00 2001 From: Oleksandr Kucherenko Date: Fri, 12 Oct 2018 16:58:31 +0200 Subject: [PATCH 06/11] updated documentation --- README.md | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/README.md b/README.md index 6611d7af..2819284e 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,81 @@ Model tested: iPhone 6 (iOS), Nexus 5 (Android). 3. Component itself lacks platform support. 4. But you can just use the react-native-maps snapshot function: https://github.com/airbnb/react-native-maps#take-snapshot-of-map +## Performance Optimization + +During profiling captured several things that influence on performance: +1) (de-)allocation of memory for bitmap +2) (de-)allocation of memory for Base64 output buffer +3) compression of bitmap to different image formats: PNG, JPG + +To solve that in code introduced several new approaches: +- reusable images, that reduce load on GC; +- reusable arrays/buffers that also reduce load on GC; +- RAW image format for avoiding expensive compression; +- ZIP deflate compression for RAW data, that works faster in compare to `Bitmap.compress` + +more details and code snippet are below. + +### RAW Images + +Introduced a new image format RAW. it correspond a ARGB array of pixels. + +Advantages: +- no compression, so its supper quick. Screenshot taking is less than 16ms; + +RAW format supported for `zip-base64`, `base64` and `tmpfile` result types. + +RAW file on disk saved in format: `${width}:${height}|${base64}` string. + +### zip-base64 + +In compare to BASE64 result string this format fast try to apply zip/deflate compression on screenshot results +and only after that convert results to base64 string. In combination zip-base64 + raw we got a super fast +approach for capturing screen views and deliver them to the react side. + +### How to work with zip-base64 and RAW format? + +```js +const fs = require('fs') +const zlib = require('zlib') +const PNG = require('pngjs').PNG +const Buffer = require('buffer').Buffer + +const format = Platform.OS === 'android' ? 'raw' : 'png' +const result = Platform.OS === 'android' ? 'zip-base64' : 'base64' + +captureRef(this.ref, { result, format }).then(data => { + // expected pattern 'width:height|', example: '1080:1731|' + const resolution = /^(\d+):(\d+)\|/g.exec(data) + const width = (resolution || ['', 0, 0])[1] + const height = (resolution || ['', 0, 0])[2] + const base64 = data.substr((resolution || [''])[0].length || 0) + + // convert from base64 to Buffer + const buffer = Buffer.from(base64, 'base64') + // un-compress data + const inflated = zlib.inflateSync(buffer) + // compose PNG + const png = new PNG({ width, height }) + png.data = inflated + const pngData = PNG.sync.write(png) + // save composed PNG + fs.writeFileSync(output, pngData) +}) +``` + +Keep in mind that packaging PNG data is a CPU consuming operation as a `zlib.inflate`. + +Hint: use `process.fork()` approach for converting raw data into PNGs. + +> Note: code is tested in large commercial project. + +> Note #2: Don't forget to add packages into your project: +> ```js +> yarn add pngjs +> yarn add zlib +> ``` + ## Troubleshooting / FAQ ### Saving to a file? From 03cd53b3c64a3156b3e0464804eecb606e2856bf Mon Sep 17 00:00:00 2001 From: Oleksandr Kucherenko Date: Fri, 12 Oct 2018 17:00:26 +0200 Subject: [PATCH 07/11] updated android dependencies --- example/android/build.gradle | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/example/android/build.gradle b/example/android/build.gradle index d9b60406..9b070b74 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -2,19 +2,34 @@ buildscript { repositories { + google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.3' + classpath 'com.android.tools.build:gradle:3.1.4' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } +// This will apply compileSdkVersion and buildToolsVersion to any android modules +subprojects { subproject -> + afterEvaluate { project -> + if (!project.name.equalsIgnoreCase("app") && project.hasProperty("android")) { + android { + compileSdkVersion 27 + buildToolsVersion '28.0.3' + } + } + } +} + + allprojects { repositories { mavenLocal() + google() jcenter() maven { // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm From 1f580f82d144b95ec68d4fa4d7819232edd8a739 Mon Sep 17 00:00:00 2001 From: Oleksandr Kucherenko Date: Fri, 12 Oct 2018 17:02:23 +0200 Subject: [PATCH 08/11] gradle version updated to 4.10.1 --- .../android/gradle/wrapper/gradle-wrapper.jar | Bin 52266 -> 54329 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- example/android/gradlew | 78 ++++++++++-------- example/android/gradlew.bat | 14 +--- example/android/settings.gradle | 3 +- 5 files changed, 51 insertions(+), 48 deletions(-) diff --git a/example/android/gradle/wrapper/gradle-wrapper.jar b/example/android/gradle/wrapper/gradle-wrapper.jar index b5166dad4d90021f6a0b45268c0755719f1d5cd4..f6b961fd5a86aa5fbfe90f707c3138408be7c718 100644 GIT binary patch delta 42314 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

!I{ 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 MR6yud!{M+00Sm=Wj{pDw diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index cac449fa..bdc7842b 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Sep 12 09:47:32 CEST 2017 +#Tue Oct 09 08:59:03 CEST 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip diff --git a/example/android/gradlew b/example/android/gradlew index 91a7e269..cccdd3d5 100755 --- a/example/android/gradlew +++ b/example/android/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh ############################################################################## ## @@ -6,20 +6,38 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# 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\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,31 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=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. @@ -90,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -114,6 +113,7 @@ fi if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` @@ -154,11 +154,19 @@ if $cygwin ; then 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=("$@") +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/example/android/gradlew.bat b/example/android/gradlew.bat index aec99730..e95643d6 100644 --- a/example/android/gradlew.bat +++ b/example/android/gradlew.bat @@ -8,14 +8,14 @@ @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 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= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,10 +46,9 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows 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. @@ -60,11 +59,6 @@ set _SKIP=2 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 diff --git a/example/android/settings.gradle b/example/android/settings.gradle index ed7e4b35..34b836d7 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -1,8 +1,9 @@ rootProject.name = 'ViewShotExample' +include ':app' + include ':react-native-svg' project(':react-native-svg').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-svg/android') -include ':app' include ':react-native-view-shot' project(':react-native-view-shot').projectDir = new File(rootProject.projectDir, '../../android') From 31ec6a0fbc5e71410c58e5ca464474258bb47a51 Mon Sep 17 00:00:00 2001 From: Oleksandr Kucherenko Date: Mon, 15 Oct 2018 10:24:38 +0200 Subject: [PATCH 09/11] resolve android support library versions clash --- example/android/build.gradle | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/example/android/build.gradle b/example/android/build.gradle index 9b070b74..217071cf 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -37,3 +37,17 @@ allprojects { } } } + + +subprojects { + configurations.all { + resolutionStrategy { + eachDependency { details -> + /* Override by group name */ + switch (details.requested.group) { + case 'com.android.support': details.useVersion '27.+'; break + } + } + } + } +} \ No newline at end of file From e394402499f0c5661c641b3e15783a94f2befc34 Mon Sep 17 00:00:00 2001 From: Oleksandr Kucherenko Date: Mon, 15 Oct 2018 10:25:46 +0200 Subject: [PATCH 10/11] added support of attaching lib as rootProject or as a sub-module --- android/build.gradle | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 27df6c1b..204676fb 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,41 +1,48 @@ buildscript { - repositories { - jcenter() - } - - dependencies { - classpath 'com.android.tools.build:gradle:2.3.0' + /* In case of submodule usage, do not try to apply own repositories and plugins, + root project is responsible for that. */ + if (rootProject.buildDir == project.buildDir) { + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.3.0' + } } } apply plugin: 'com.android.library' android { - compileSdkVersion 26 - buildToolsVersion "26.0.1" + compileSdkVersion 27 + buildToolsVersion "28.0.3" defaultConfig { minSdkVersion 16 - targetSdkVersion 26 + targetSdkVersion 27 + versionCode 1 versionName "1.0" } + lintOptions { abortOnError false } } -allprojects { - repositories { - mavenLocal() - jcenter() - maven { - // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm - url "$rootDir/../node_modules/react-native/android" - } +repositories { + google() + jcenter() + mavenLocal() + maven { + // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm + url "$rootDir/../node_modules/react-native/android" } } dependencies { - compile 'com.facebook.react:react-native:+' + implementation 'com.android.support:support-v4:27.+' + + api 'com.facebook.react:react-native:+' } \ No newline at end of file From b7e9d6eae4269911a3d9813de1ae3f21970af2f2 Mon Sep 17 00:00:00 2001 From: Oleksandr Kucherenko Date: Thu, 25 Oct 2018 11:45:45 +0200 Subject: [PATCH 11/11] fix the child draw matrix/transformation rendering issues --- .../greweb/reactnativeviewshot/ViewShot.java | 28 ++++++++++++++++++- example/App.js | 28 ++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/fr/greweb/reactnativeviewshot/ViewShot.java b/android/src/main/java/fr/greweb/reactnativeviewshot/ViewShot.java index 313fb7b1..46ed94d4 100644 --- a/android/src/main/java/fr/greweb/reactnativeviewshot/ViewShot.java +++ b/android/src/main/java/fr/greweb/reactnativeviewshot/ViewShot.java @@ -4,8 +4,10 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; +import android.graphics.RectF; import android.net.Uri; import android.support.annotation.IntDef; import android.support.annotation.NonNull; @@ -14,6 +16,7 @@ import android.view.TextureView; import android.view.View; import android.view.ViewGroup; +import android.view.ViewParent; import android.widget.ScrollView; import com.facebook.react.bridge.Promise; @@ -328,6 +331,8 @@ private Point captureViewImpl(@NonNull final View view, @NonNull final OutputStr for (final View child : childrenList) { // skip any child that we don't know how to process if (!(child instanceof TextureView)) continue; + // skip all invisible to user child views + if (child.getVisibility() != View.VISIBLE) continue; final TextureView tvChild = (TextureView) child; tvChild.setOpaque(false); @@ -338,12 +343,16 @@ private Point captureViewImpl(@NonNull final View view, @NonNull final OutputStr final int childWidth = child.getWidth(); final int childHeight = child.getHeight(); final Rect source = new Rect(0, 0, childWidth, childHeight); - final Rect destination = new Rect(left, top, left + childWidth, top + childHeight); + final RectF destination = new RectF(left, top, left + childWidth, top + childHeight); // get re-usable bitmap final Bitmap childBitmapBuffer = tvChild.getBitmap(getBitmapForScreenshot(child.getWidth(), child.getHeight())); + + c.save(); + c.setMatrix(concatMatrix(view, child)); // due to re-use of bitmaps for screenshot, we can get bitmap that is bigger in size than requested c.drawBitmap(childBitmapBuffer, source, destination, null); + c.restore(); recycleBitmap(childBitmapBuffer); } @@ -371,6 +380,23 @@ private Point captureViewImpl(@NonNull final View view, @NonNull final OutputStr return resolution; // return image width and height } + /** Concat all the transformation matrix's from child to parent. */ + @NonNull + private Matrix concatMatrix(@NonNull final View view, @NonNull final View child){ + final Matrix transform = new Matrix(); + + View iterator = child; + do { + + final Matrix m = iterator.getMatrix(); + transform.preConcat(m); + + iterator = (View)iterator.getParent(); + } while( iterator != view ); + + return transform; + } + @NonNull private Point getParentOffsets(@NonNull final View view, @NonNull final View child) { int left = 0; diff --git a/example/App.js b/example/App.js index b60fcbb1..63f98f3d 100644 --- a/example/App.js +++ b/example/App.js @@ -9,7 +9,8 @@ import { TextInput, Picker, Slider, - WebView + WebView, + ART } from "react-native"; import SvgUri from "react-native-svg-uri"; import omit from "lodash/omit"; @@ -148,6 +149,8 @@ export default class App extends Component { /> + + @@ -169,6 +172,7 @@ export default class App extends Component { + @@ -239,6 +243,7 @@ export default class App extends Component { > + @@ -258,6 +263,24 @@ export default class App extends Component { Experimental Stuff + + + Transform + + Sample Text + + + + +