Skip to content

[bugfix] View cuts off on Android #540

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 90 additions & 73 deletions android/src/main/java/fr/greweb/reactnativeviewshot/ViewShot.java
Original file line number Diff line number Diff line change
Expand Up @@ -357,16 +357,23 @@ private Point captureViewImpl(@NonNull final View view, @NonNull final OutputStr
if (w <= 0 || h <= 0) {
throw new RuntimeException("Impossible to snapshot the view: view is invalid");
}

// evaluate real height
if (snapshotContentContainer) {
h = 0;

// Evaluate real height for ScrollView
if (snapshotContentContainer && view instanceof ScrollView) {
ScrollView scrollView = (ScrollView) view;
for (int i = 0; i < scrollView.getChildCount(); i++) {
h += scrollView.getChildAt(i).getHeight();
}
View content = scrollView.getChildAt(0);
h = content.getHeight() + scrollView.getPaddingTop() + scrollView.getPaddingBottom();
}

Log.d("ViewCapture", "Final capture dimensions: " + w + "x" + h);

// Force layout measurement
view.measure(
View.MeasureSpec.makeMeasureSpec(w, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(h, View.MeasureSpec.EXACTLY)
);
view.layout(0, 0, w, h);

final Point resolution = new Point(w, h);
Bitmap bitmap = getBitmapForScreenshot(w, h);

Expand All @@ -375,89 +382,99 @@ private Point captureViewImpl(@NonNull final View view, @NonNull final OutputStr
paint.setFilterBitmap(true);
paint.setDither(true);

// Uncomment next line if you want to wait attached android studio debugger:
// Debug.waitForDebugger();

final Canvas c = new Canvas(bitmap);
view.draw(c);

//after view is drawn, go through children
final List<View> childrenList = getAllChildren(view);

for (final View child : childrenList) {
// skip any child that we don't know how to process
if (child instanceof TextureView) {
// skip all invisible to user child views
if (child.getVisibility() != VISIBLE) continue;

final TextureView tvChild = (TextureView) child;
tvChild.setOpaque(false); // <-- switch off background fill

// NOTE (olku): get re-usable bitmap. TextureView should use bitmaps with matching size,
// otherwise content of the TextureView will be scaled to provided bitmap dimensions
final Bitmap childBitmapBuffer = tvChild.getBitmap(getExactBitmapForScreenshot(child.getWidth(), child.getHeight()));

final int countCanvasSave = c.save();
applyTransformations(c, view, child);

// due to re-use of bitmaps for screenshot, we can get bitmap that is bigger in size than requested
c.drawBitmap(childBitmapBuffer, 0, 0, paint);

c.restoreToCount(countCanvasSave);
recycleBitmap(childBitmapBuffer);
} else if (child instanceof SurfaceView && handleGLSurfaceView) {
final SurfaceView svChild = (SurfaceView)child;
final CountDownLatch latch = new CountDownLatch(1);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
final Bitmap childBitmapBuffer = getExactBitmapForScreenshot(child.getWidth(), child.getHeight());
try {
PixelCopy.request(svChild, childBitmapBuffer, new PixelCopy.OnPixelCopyFinishedListener() {
@Override
public void onPixelCopyFinished(int copyResult) {
final int countCanvasSave = c.save();
applyTransformations(c, view, child);
c.drawBitmap(childBitmapBuffer, 0, 0, paint);
c.restoreToCount(countCanvasSave);
recycleBitmap(childBitmapBuffer);
latch.countDown();
}
}, new Handler(Looper.getMainLooper()));
latch.await(SURFACE_VIEW_READ_PIXELS_TIMEOUT, TimeUnit.SECONDS);
} catch (Exception e) {
Log.e(TAG, "Cannot PixelCopy for " + svChild, e);
}
} else {
Bitmap cache = svChild.getDrawingCache();
if (cache != null) {
c.drawBitmap(svChild.getDrawingCache(), 0, 0, paint);
}
}
}
}
captureViewHierarchy(c, view, paint);

if (width != null && height != null && (width != w || height != h)) {
final Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, width, height, true);
recycleBitmap(bitmap);
Log.d("ViewCapture", "Scaling bitmap from " + w + "x" + h + " to " + width + "x" + height);
float scaleWidth = width / (float) w;
float scaleHeight = height / (float) h;
float scale = Math.min(scaleWidth, scaleHeight);

Matrix matrix = new Matrix();
matrix.postScale(scale, scale);

final Bitmap scaledBitmap = Bitmap.createBitmap(bitmap, 0, 0, w, h, matrix, true);
recycleBitmap(bitmap);
bitmap = scaledBitmap;
}

// special case, just save RAW ARGB array without any compression
// Save bitmap
if (Formats.RAW == this.format && os instanceof ReusableByteArrayOutputStream) {
final int total = w * h * ARGB_SIZE;
final ReusableByteArrayOutputStream rbaos = cast(os);
final int total = bitmap.getWidth() * bitmap.getHeight() * ARGB_SIZE;
final ReusableByteArrayOutputStream rbaos = (ReusableByteArrayOutputStream) 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 resolution; // return image width and height
private void captureViewHierarchy(Canvas canvas, View view, Paint paint) {
Log.d("ViewCapture", "Capturing view: " + view.getClass().getSimpleName()
+ " at (" + view.getLeft() + "," + view.getTop()
+ ") with size (" + view.getWidth() + "x" + view.getHeight() + ")");

int saveCount = canvas.save();
canvas.translate(view.getLeft(), view.getTop());

// Draw the view itself
view.draw(canvas);

if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View child = viewGroup.getChildAt(i);
if (child.getVisibility() == View.VISIBLE) {
if (child instanceof TextureView) {
captureTextureView(canvas, (TextureView) child, paint);
} else if (child instanceof SurfaceView && handleGLSurfaceView) {
captureSurfaceView(canvas, (SurfaceView) child, paint);
} else {
captureViewHierarchy(canvas, child, paint);
}
}
}
}

canvas.restoreToCount(saveCount);
}

private void captureTextureView(Canvas canvas, TextureView textureView, Paint paint) {
textureView.setOpaque(false);
Bitmap childBitmapBuffer = textureView.getBitmap(getExactBitmapForScreenshot(textureView.getWidth(), textureView.getHeight()));
canvas.drawBitmap(childBitmapBuffer, 0, 0, paint);
recycleBitmap(childBitmapBuffer);
}

private void captureSurfaceView(Canvas canvas, SurfaceView surfaceView, Paint paint) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
final CountDownLatch latch = new CountDownLatch(1);
final Bitmap childBitmapBuffer = getExactBitmapForScreenshot(surfaceView.getWidth(), surfaceView.getHeight());
try {
PixelCopy.request(surfaceView, childBitmapBuffer, (copyResult) -> {
if (copyResult == PixelCopy.SUCCESS) {
canvas.drawBitmap(childBitmapBuffer, 0, 0, paint);
} else {
Log.e("ViewCapture", "Failed to copy pixels from SurfaceView");
}
recycleBitmap(childBitmapBuffer);
latch.countDown();
}, new Handler(Looper.getMainLooper()));
latch.await(SURFACE_VIEW_READ_PIXELS_TIMEOUT, TimeUnit.SECONDS);
} catch (Exception e) {
Log.e("ViewCapture", "Error during PixelCopy for SurfaceView", e);
}
} else {
Bitmap cache = surfaceView.getDrawingCache();
if (cache != null) {
canvas.drawBitmap(cache, 0, 0, paint);
}
}
}

/**
Expand Down