Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.renderer.RenderSurface;

/**
* Paints a Flutter UI provided by an {@link android.media.ImageReader} onto a {@link
Expand All @@ -32,10 +34,12 @@
*/
@SuppressLint("ViewConstructor")
@TargetApi(19)
public class FlutterImageView extends View {
public class FlutterImageView extends View implements RenderSurface {
private final ImageReader imageReader;
@Nullable private Image nextImage;
@Nullable private Image currentImage;
@Nullable private Bitmap currentBitmap;
@Nullable private FlutterRenderer flutterRenderer;

/**
* Constructs a {@code FlutterImageView} with an {@link android.media.ImageReader} that provides
Expand All @@ -46,6 +50,37 @@ public FlutterImageView(@NonNull Context context, @NonNull ImageReader imageRead
this.imageReader = imageReader;
}

@Nullable
@Override
public FlutterRenderer getAttachedRenderer() {
return flutterRenderer;
}

/**
* Invoked by the owner of this {@code FlutterImageView} when it wants to begin rendering a
* Flutter UI to this {@code FlutterImageView}.
*/
@Override
public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) {
if (this.flutterRenderer != null) {
this.flutterRenderer.stopRenderingToSurface();
}

this.flutterRenderer = flutterRenderer;
flutterRenderer.startRenderingToSurface(imageReader.getSurface());
}

/**
* Invoked by the owner of this {@code FlutterImageView} when it no longer wants to render a
* Flutter UI to this {@code FlutterImageView}.
*/
public void detachFromRenderer() {
if (flutterRenderer != null) {
flutterRenderer.stopRenderingToSurface();
flutterRenderer = null;
}
}

/** Acquires the next image to be drawn to the {@link android.graphics.Canvas}. */
@TargetApi(19)
public void acquireLatestImage() {
Expand All @@ -56,51 +91,44 @@ public void acquireLatestImage() {
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (nextImage == null) {
return;
}

if (currentImage != null) {
currentImage.close();
if (nextImage != null) {
if (currentImage != null) {
currentImage.close();
}
currentImage = nextImage;
nextImage = null;
updateCurrentBitmap();
}
currentImage = nextImage;
nextImage = null;

if (android.os.Build.VERSION.SDK_INT >= 29) {
drawImageBuffer(canvas);
return;
if (currentBitmap != null) {
canvas.drawBitmap(currentBitmap, 0, 0, null);
}

drawImagePlane(canvas);
}

@TargetApi(29)
private void drawImageBuffer(@NonNull Canvas canvas) {
final HardwareBuffer buffer = currentImage.getHardwareBuffer();

final Bitmap bitmap = Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB));
canvas.drawBitmap(bitmap, 0, 0, null);
}

private void drawImagePlane(@NonNull Canvas canvas) {
if (currentImage == null) {
return;
}

final Plane[] imagePlanes = currentImage.getPlanes();
if (imagePlanes.length != 1) {
return;
private void updateCurrentBitmap() {
if (android.os.Build.VERSION.SDK_INT >= 29) {
final HardwareBuffer buffer = currentImage.getHardwareBuffer();
currentBitmap = Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB));
} else {
final Plane[] imagePlanes = currentImage.getPlanes();
if (imagePlanes.length != 1) {
return;
}

final Plane imagePlane = imagePlanes[0];
final int desiredWidth = imagePlane.getRowStride() / imagePlane.getPixelStride();
final int desiredHeight = currentImage.getHeight();

if (currentBitmap == null
|| currentBitmap.getWidth() != desiredWidth
|| currentBitmap.getHeight() != desiredHeight) {
currentBitmap =
Bitmap.createBitmap(
desiredWidth, desiredHeight, android.graphics.Bitmap.Config.ARGB_8888);
}

currentBitmap.copyPixelsFromBuffer(imagePlane.getBuffer());
}

final Plane imagePlane = imagePlanes[0];
final int desiredWidth = imagePlane.getRowStride() / imagePlane.getPixelStride();
final int desiredHeight = currentImage.getHeight();

final Bitmap bitmap =
android.graphics.Bitmap.createBitmap(
desiredWidth, desiredHeight, android.graphics.Bitmap.Config.ARGB_8888);

bitmap.copyPixelsFromBuffer(imagePlane.getBuffer());
canvas.drawBitmap(bitmap, 0, 0, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import android.content.res.Configuration;
import android.graphics.Insets;
import android.graphics.Rect;
import android.media.ImageReader;
import android.os.Build;
import android.text.format.DateFormat;
import android.util.AttributeSet;
Expand Down Expand Up @@ -303,6 +304,7 @@ private FlutterView(
super(context, attrs);

this.flutterImageView = flutterImageView;
this.renderSurface = flutterImageView;

init();
}
Expand Down Expand Up @@ -940,6 +942,32 @@ public void detachFromFlutterEngine() {
flutterEngine = null;
}

public void convertToImageView() {
renderSurface.detachFromRenderer();

ImageReader imageReader = PlatformViewsController.createImageReader(getWidth(), getHeight());
flutterImageView = new FlutterImageView(getContext(), imageReader);
renderSurface = flutterImageView;
if (flutterEngine != null) {
renderSurface.attachToRenderer(flutterEngine.getRenderer());
}

removeAllViews();
addView(flutterImageView);

// TODO(jsimmons): this is a temporary hack that schedules a redraw of the FlutterImageView
// at a time when the engine has presumably posted a frame. Remove this when
// PlatformViewsController.onEndFrame callbacks have been implemented.
postDelayed(
new Runnable() {
public void run() {
flutterImageView.acquireLatestImage();
flutterImageView.invalidate();
}
},
1000);
}

/** Returns true if this {@code FlutterView} is currently attached to a {@link FlutterEngine}. */
@VisibleForTesting
public boolean isAttachedToFlutterEngine() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
import io.flutter.embedding.android.FlutterImageView;
import io.flutter.embedding.android.FlutterView;
import io.flutter.embedding.engine.FlutterOverlaySurface;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.systemchannels.PlatformViewsChannel;
Expand Down Expand Up @@ -79,9 +80,12 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
// Map of unique IDs to views that render overlay layers.
private final LongSparseArray<FlutterImageView> overlayLayerViews;

// Next available unique ID for use in overlayLayerViews;
// Next available unique ID for use in overlayLayerViews.
private long nextOverlayLayerId = 0;

// Tracks whether the flutterView has been converted to use a FlutterImageView.
private boolean flutterViewConvertedToImageView = false;

private final PlatformViewsChannel.PlatformViewsHandler channelHandler =
new PlatformViewsChannel.PlatformViewsHandler() {
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
Expand Down Expand Up @@ -552,7 +556,10 @@ public void onDisplayPlatformView(int viewId, int x, int y, int width, int heigh
}

public void onDisplayOverlaySurface(int id, int x, int y, int width, int height) {
// TODO: Implement this method. https://github.com/flutter/flutter/issues/58288
if (!flutterViewConvertedToImageView) {
((FlutterView) flutterView).convertToImageView();
flutterViewConvertedToImageView = true;
}
}

public void onBeginFrame() {
Expand All @@ -564,22 +571,22 @@ public void onEndFrame() {
}

@TargetApi(19)
public FlutterOverlaySurface createOverlaySurface() {
ImageReader imageReader;
public static ImageReader createImageReader(int width, int height) {
if (android.os.Build.VERSION.SDK_INT >= 29) {
imageReader =
ImageReader.newInstance(
flutterView.getWidth(),
flutterView.getHeight(),
PixelFormat.RGBA_8888,
2,
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT);
return ImageReader.newInstance(
width,
height,
PixelFormat.RGBA_8888,
2,
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT);
} else {
imageReader =
ImageReader.newInstance(
flutterView.getWidth(), flutterView.getHeight(), PixelFormat.RGBA_8888, 2);
return ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 2);
}
}

@TargetApi(19)
public FlutterOverlaySurface createOverlaySurface() {
ImageReader imageReader = createImageReader(flutterView.getWidth(), flutterView.getHeight());
FlutterImageView imageView = new FlutterImageView(flutterView.getContext(), imageReader);
long id = nextOverlayLayerId++;
overlayLayerViews.put(id, imageView);
Expand Down