blob: c5fc13fce0aa94624e679a1b6f1c5e73b266af89 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tools.idea.validator;
import com.android.tools.idea.validator.ValidatorResult.ImageSize;
import com.android.tools.idea.validator.ValidatorResult.Metric;
import com.android.tools.layoutlib.annotations.NotNull;
import android.annotation.NonNull;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import com.google.android.apps.common.testing.accessibility.framework.utils.contrast.Image;
import javax.imageio.ImageIO;
import static java.awt.image.BufferedImage.TYPE_INT_ARGB;
/**
* Image implementation to be used in Accessibility Test Framework.
*/
public class AtfBufferedImage implements Image {
// The source buffered image, expected to contain the full screen rendered image of the layout.
@NotNull private final BufferedImage mImage;
// Metrics to be returned
@NotNull private final Metric mMetric;
// Points in before scaled coord
private final int mLeft;
private final int mTop;
private final int mWidth;
private final int mHeight;
// Scale factors in case layoutlib scaled the screen.
private final float mScaleX;
private final float mScaleY;
AtfBufferedImage(
@NotNull BufferedImage image,
@NotNull Metric metric,
float scaleX,
float scaleY) {
// Without unscaling, atf does not recognize bounds that goes over the scaled image.
// E.g. if pxl4 is scaled to 1k x 2k (originally 2k x 4k), then atf could request for
// bounds at x:1.5k y:0, then without unscaling the call would fail.
this(image,
metric,
0,
0,
(int) (image.getWidth() * 1.0f / scaleX),
(int) (image.getHeight() * 1.0f / scaleY),
scaleX,
scaleY);
assert(image.getType() == TYPE_INT_ARGB);
// FOR DEBUGGING ONLY
if (LayoutValidator.shouldSaveCroppedImages()) {
saveImage(image);
}
}
private AtfBufferedImage(
@NotNull BufferedImage image,
@NotNull Metric metric,
int left,
int top,
int width,
int height,
float scaleX,
float scaleY) {
mImage = image;
mMetric = metric;
mLeft = left;
mTop = top;
mWidth = width;
mHeight = height;
mScaleX = scaleX;
mScaleY = scaleY;
}
@Override
public int getHeight() {
return mHeight;
}
@Override
public int getWidth() {
return mWidth;
}
@Override
@NotNull
public Image crop(int left, int top, int width, int height) {
return new AtfBufferedImage(mImage, mMetric, left, top, width, height, mScaleX, mScaleY);
}
/**
* @return the region that matches the scaled buffered image. The returned image
* will not have the width same as {@link #getWidth()} but rather width * mScaleX.
*/
@Override
@NotNull
public int[] getPixels() {
int scaledLeft = (int)(mLeft * mScaleX);
int scaledTop = (int)(mTop * mScaleY);
int scaledWidth = (int)(mWidth * mScaleX);
int scaledHeight = (int)(mHeight * mScaleY);
if (scaledWidth <= 0 || scaledHeight <= 0) {
return new int[0];
}
BufferedImage cropped = mImage.getSubimage(
scaledLeft, scaledTop, scaledWidth, scaledHeight);
WritableRaster raster =
cropped.copyData(cropped.getRaster().createCompatibleWritableRaster());
int[] toReturn = ((DataBufferInt) raster.getDataBuffer()).getData();
mMetric.mImageMemoryBytes += toReturn.length * 4;
if (LayoutValidator.shouldSaveCroppedImages()) {
saveImage(cropped);
}
return toReturn;
}
// FOR DEBUGGING ONLY
private static int SAVE_IMAGE_COUNTER = 0;
private void saveImage(BufferedImage image) {
try {
String name = SAVE_IMAGE_COUNTER + "_img_for_atf_LxT:WxH_" +
mLeft + "x" + mTop + ":" +
mWidth + "x" + mHeight;
mMetric.mImageSizes.add(new ImageSize(mLeft, mTop, mWidth, mHeight));
File output = new File(getDebugDir(), name);
if (output.exists()) {
output.delete();
}
ImageIO.write(image, "PNG", output);
SAVE_IMAGE_COUNTER++;
} catch (IOException ioe) {
mMetric.mErrorMessage = ioe.getMessage();
}
}
@NonNull
private File getDebugDir() {
File failureDir;
String failureDirString = System.getProperty("debug.dir");
if (failureDirString != null) {
failureDir = new File(failureDirString);
} else {
String workingDirString = System.getProperty("user.dir");
failureDir = new File(workingDirString, "out/debugs");
}
failureDir.mkdirs();
return failureDir;
}
}