From 7ff9615b08822fe69ea4a86eceb1991e83e3e3db Mon Sep 17 00:00:00 2001 From: Mohammad Saboorian Date: Mon, 14 Mar 2022 16:06:40 +0000 Subject: Remove system drawn frame for round emulators Playstore guidelines for Wear OS apps doesn't allow transparent background or device frames in the screenshot (https://support.google.com/googleplay/android-developer/answer/9866151#zippy=%2Cscreenshots). The change modifies the rectangular option to generates a play compliant screenshot. Bug: 211018814 Test: existing Change-Id: Ie4a14a39d5975b94a36111dbee9462b15efe0ea0 --- .../screenshot/DeviceArtScreenshotPostprocessor.kt | 12 ++++++-- .../ddms/screenshot/ScreenshotPostprocessor.kt | 4 ++- .../idea/ddms/screenshot/ScreenshotViewer.java | 27 ++++++++++------- .../idea/ddms/screenshot/ScreenshotViewerTest.kt | 34 ++++++++++++++++++++-- 4 files changed, 60 insertions(+), 17 deletions(-) (limited to 'android') diff --git a/android/src/com/android/tools/idea/ddms/screenshot/DeviceArtScreenshotPostprocessor.kt b/android/src/com/android/tools/idea/ddms/screenshot/DeviceArtScreenshotPostprocessor.kt index 5d2197e6343..a9bfc406a34 100644 --- a/android/src/com/android/tools/idea/ddms/screenshot/DeviceArtScreenshotPostprocessor.kt +++ b/android/src/com/android/tools/idea/ddms/screenshot/DeviceArtScreenshotPostprocessor.kt @@ -20,6 +20,7 @@ import com.android.tools.adtui.ImageUtils import com.android.tools.adtui.device.DeviceArtPainter import com.intellij.util.ui.ImageUtil.applyQualityRenderingHints import java.awt.AlphaComposite +import java.awt.Color import java.awt.geom.Area import java.awt.geom.Ellipse2D import java.awt.image.BufferedImage @@ -30,10 +31,10 @@ import kotlin.math.max */ class DeviceArtScreenshotPostprocessor : ScreenshotPostprocessor { @Slow - override fun addFrame(screenshotImage: ScreenshotImage, framingOption: FramingOption?): BufferedImage { + override fun addFrame(screenshotImage: ScreenshotImage, framingOption: FramingOption?, backgroundColor: Color?): BufferedImage { screenshotImage as DeviceScreenshotImage if (framingOption == null) { - return if (screenshotImage.isRoundScreen) circularClip(screenshotImage.image) else screenshotImage.image + return if (screenshotImage.isRoundScreen) circularClip(screenshotImage.image, backgroundColor) else screenshotImage.image } val frameDescriptor = (framingOption as DeviceArtFramingOption).deviceArtDescriptor val framedImage = DeviceArtPainter.createFrame(screenshotImage.image, frameDescriptor, false, false) @@ -41,7 +42,7 @@ class DeviceArtScreenshotPostprocessor : ScreenshotPostprocessor { } @Suppress("UndesirableClassUsage") - private fun circularClip(image: BufferedImage): BufferedImage { + private fun circularClip(image: BufferedImage, backgroundColor: Color?): BufferedImage { val mask = BufferedImage(image.width, image.height, BufferedImage.TYPE_INT_ARGB) mask.createGraphics().apply { applyQualityRenderingHints(this) @@ -55,6 +56,11 @@ class DeviceArtScreenshotPostprocessor : ScreenshotPostprocessor { drawImage(image, 0, 0, null) composite = AlphaComposite.getInstance(AlphaComposite.DST_IN) drawImage(mask, 0, 0, null) + if (backgroundColor != null) { + color = backgroundColor + composite = AlphaComposite.getInstance(AlphaComposite.DST_OVER) + fillRect(0, 0, image.width, image.height) + } dispose() } return shapedImage diff --git a/android/src/com/android/tools/idea/ddms/screenshot/ScreenshotPostprocessor.kt b/android/src/com/android/tools/idea/ddms/screenshot/ScreenshotPostprocessor.kt index 9b3ebd45053..b84ad8b6e17 100644 --- a/android/src/com/android/tools/idea/ddms/screenshot/ScreenshotPostprocessor.kt +++ b/android/src/com/android/tools/idea/ddms/screenshot/ScreenshotPostprocessor.kt @@ -31,6 +31,7 @@ package com.android.tools.idea.ddms.screenshot import com.android.annotations.concurrency.Slow +import java.awt.Color import java.awt.image.BufferedImage /** @@ -43,8 +44,9 @@ interface ScreenshotPostprocessor { * @param screenshotImage the screenshot image to process * @param framingOption determines the type of the frame to add to the image, or null to possibly * adjust the screenshot without adding a frame + * @param backgroundColor the back color to use when clipping the screenshot * @return the framed image */ @Slow - fun addFrame(screenshotImage: ScreenshotImage, framingOption: FramingOption?): BufferedImage + fun addFrame(screenshotImage: ScreenshotImage, framingOption: FramingOption?, backgroundColor: Color?): BufferedImage } \ No newline at end of file diff --git a/android/src/com/android/tools/idea/ddms/screenshot/ScreenshotViewer.java b/android/src/com/android/tools/idea/ddms/screenshot/ScreenshotViewer.java index 5707692ba4a..76cc9c2c7e0 100644 --- a/android/src/com/android/tools/idea/ddms/screenshot/ScreenshotViewer.java +++ b/android/src/com/android/tools/idea/ddms/screenshot/ScreenshotViewer.java @@ -55,6 +55,7 @@ import com.intellij.openapi.vfs.VirtualFileWrapper; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.util.xmlb.XmlSerializerUtil; import java.awt.BorderLayout; +import java.awt.Color; import java.awt.Component; import java.awt.color.ICC_ColorSpace; import java.awt.datatransfer.DataFlavor; @@ -121,7 +122,7 @@ public class ScreenshotViewer extends DialogWrapper implements DataProvider { private @NotNull JButton myCopyButton; private static final class DecorationOption { - private static final DecorationOption NO_DECORATION = new DecorationOption("Rectangular"); + private static final DecorationOption RECTANGULAR = new DecorationOption("Rectangular"); private static final DecorationOption DISPLAY_SHAPE_CLIP = new DecorationOption("Display Shape"); private final @Nullable String myClipAction; @@ -248,7 +249,7 @@ public class ScreenshotViewer extends DialogWrapper implements DataProvider { canClipDeviceMask = ((DeviceScreenshotImage)screenshotImage).isRoundScreen(); } DefaultComboBoxModel decorationOptions = new DefaultComboBoxModel<>(); - decorationOptions.addElement(DecorationOption.NO_DECORATION); + decorationOptions.addElement(DecorationOption.RECTANGULAR); if (canClipDeviceMask) { decorationOptions.addElement(DecorationOption.DISPLAY_SHAPE_CLIP); } @@ -262,8 +263,9 @@ public class ScreenshotViewer extends DialogWrapper implements DataProvider { myDecorationComboBox.setSelectedIndex(defaultFramingOption + frameOptionStartIndex); // Select the default framing option. } else { - // DEVICE_SHAPED or ORIGINAL (if DEVICE_SHAPED is not available). - myDecorationComboBox.setSelectedItem(canClipDeviceMask ? DecorationOption.DISPLAY_SHAPE_CLIP : DecorationOption.NO_DECORATION); + // DEVICE_SHAPED or RECTANGULAR (if DEVICE_SHAPED is not available). + myDecorationComboBox.setSelectedItem( + canClipDeviceMask ? DecorationOption.DISPLAY_SHAPE_CLIP : DecorationOption.RECTANGULAR); } ActionListener decorationListener = event -> { @@ -361,15 +363,17 @@ public class ScreenshotViewer extends DialogWrapper implements DataProvider { private void processScreenshot(int rotateByQuadrants) { FramingOption framingOption = null; - boolean needsProcessing = false; + Color backgroundColor = null; if (myScreenshotPostprocessor != null) { framingOption = ((DecorationOption)myDecorationComboBox.getSelectedItem()).getFramingOption(); - needsProcessing = !myDecorationComboBox.getSelectedItem().equals(DecorationOption.NO_DECORATION); + if (myDecorationComboBox.getSelectedItem().equals(DecorationOption.RECTANGULAR)) { + backgroundColor = Color.BLACK; + } } new ImageProcessorTask(myProject, mySourceImageRef.get(), rotateByQuadrants, - needsProcessing ? myScreenshotPostprocessor : null, framingOption, - myBackingFile) { + myScreenshotPostprocessor, framingOption, + myBackingFile, backgroundColor) { @Override public void onSuccess() { mySourceImageRef.set(getRotatedImage()); @@ -385,6 +389,7 @@ public class ScreenshotViewer extends DialogWrapper implements DataProvider { private final @Nullable ScreenshotPostprocessor myScreenshotPostprocessor; private final @Nullable FramingOption myFramingOption; private final @Nullable VirtualFile myDestinationFile; + private final @Nullable Color myBackgroundColor; private ScreenshotImage myRotatedImage; private BufferedImage myProcessedImage; @@ -394,7 +399,8 @@ public class ScreenshotViewer extends DialogWrapper implements DataProvider { int rotateByQuadrants, @Nullable ScreenshotPostprocessor screenshotPostprocessor, @Nullable FramingOption framingOption, - @Nullable VirtualFile writeToFile) { + @Nullable VirtualFile writeToFile, + @Nullable Color backgroundColor) { super(project, AndroidBundle.message("android.ddms.screenshot.image.processor.task.title"), false); mySrcImage = srcImage; @@ -402,6 +408,7 @@ public class ScreenshotViewer extends DialogWrapper implements DataProvider { myScreenshotPostprocessor = screenshotPostprocessor; myFramingOption = framingOption; myDestinationFile = writeToFile; + myBackgroundColor = backgroundColor; } @Override @@ -412,7 +419,7 @@ public class ScreenshotViewer extends DialogWrapper implements DataProvider { myProcessedImage = myRotatedImage.getImage(); } else { - myProcessedImage = myScreenshotPostprocessor.addFrame(myRotatedImage, myFramingOption); + myProcessedImage = myScreenshotPostprocessor.addFrame(myRotatedImage, myFramingOption, myBackgroundColor); } // Update the backing file, this is necessary for operations that read the backing file from the editor, diff --git a/android/testSrc/com/android/tools/idea/ddms/screenshot/ScreenshotViewerTest.kt b/android/testSrc/com/android/tools/idea/ddms/screenshot/ScreenshotViewerTest.kt index b5118d0f97b..3c543f0f7b8 100644 --- a/android/testSrc/com/android/tools/idea/ddms/screenshot/ScreenshotViewerTest.kt +++ b/android/testSrc/com/android/tools/idea/ddms/screenshot/ScreenshotViewerTest.kt @@ -31,7 +31,6 @@ import com.intellij.testFramework.RunsInEdt import com.intellij.util.ui.EdtInvocationManager.dispatchAllInvocationEvents import org.intellij.images.ui.ImageComponent import org.intellij.images.ui.ImageComponentDecorator -import org.jetbrains.kotlin.idea.gradleTooling.get import org.junit.After import org.junit.Before import org.junit.Rule @@ -39,6 +38,7 @@ import org.junit.Test import java.awt.Color import java.awt.image.BufferedImage import java.util.EnumSet +import javax.swing.JComboBox /** * Tests for [ScreenshotViewer]. @@ -101,19 +101,38 @@ class ScreenshotViewerTest { val screenshotImage = DeviceScreenshotImage(createImage(200, 180), 0, true) val viewer = createScreenshotViewer(screenshotImage, DeviceArtScreenshotPostprocessor()) val ui = FakeUi(viewer.rootPane) + val clipComboBox = ui.getComponent>() + clipComboBox.selectFirstMatch("Display Shape") dispatchAllInvocationEvents() PlatformTestUtil.dispatchAllEventsInIdeEventQueue() val processedImage: BufferedImage = ui.getComponent().document.value - assertThat(processedImage.getRGB(screenshotImage.width / 2, screenshotImage.height / 2)).isEqualTo(Color.WHITE.rgb) + assertThat(processedImage.getRGB(screenshotImage.width / 2, screenshotImage.height / 2)).isEqualTo(Color.RED.rgb) assertThat(processedImage.getRGB(5, 5)).isEqualTo(0) assertThat(processedImage.getRGB(screenshotImage.width - 5, screenshotImage.height - 5)).isEqualTo(0) } + @Test + fun testClipRoundScreenshotWithBackgroundColor() { + val screenshotImage = DeviceScreenshotImage(createImage(200, 180), 0, true) + val viewer = createScreenshotViewer(screenshotImage, DeviceArtScreenshotPostprocessor()) + val ui = FakeUi(viewer.rootPane) + + val clipComboBox = ui.getComponent>() + + clipComboBox.selectFirstMatch("Rectangular") + dispatchAllInvocationEvents() + PlatformTestUtil.dispatchAllEventsInIdeEventQueue() + val processedImage: BufferedImage = ui.getComponent().document.value + assertThat(processedImage.getRGB(screenshotImage.width / 2, screenshotImage.height / 2)).isEqualTo(Color.RED.rgb) + assertThat(processedImage.getRGB(5, 5)).isEqualTo(Color.BLACK.rgb) + assertThat(processedImage.getRGB(screenshotImage.width - 5, screenshotImage.height - 5)).isEqualTo(Color.BLACK.rgb) + } + private fun createImage(width: Int, height: Int): BufferedImage { val image = ImageUtils.createDipImage(width, height, BufferedImage.TYPE_INT_ARGB) val graphics = image.createGraphics() - graphics.paint = Color.WHITE + graphics.paint = Color.RED graphics.fillRect(0, 0, image.width, image.height) graphics.dispose() return image @@ -127,4 +146,13 @@ class ScreenshotViewerTest { viewer.show() return viewer } + + private fun JComboBox.selectFirstMatch(text: String) { + for (i in 0 until model.size) { + if (model.getElementAt(i).toString() == text) { + this.selectedIndex = i + return + } + } + } } \ No newline at end of file -- cgit v1.2.3