blob: ddbe625b05e3b6905b3f9d94cba2cff3ae3baaf6 [file] [log] [blame]
/*
* Copyright (C) 2014 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.builder.png;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.ide.common.internal.PngCruncher;
import com.android.ide.common.internal.PngException;
import com.android.sdklib.BuildToolInfo;
import com.android.sdklib.SdkManager;
import com.android.sdklib.repository.FullRevision;
import com.android.testutils.TestUtils;
import com.android.utils.ILogger;
import com.android.utils.StdLogger;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.google.common.io.Files;
import org.junit.Assert;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import java.util.zip.DataFormatException;
import javax.imageio.ImageIO;
/**
* Utilities common to tests for both the synchronous and the asynchronous Aapt processor.
*/
public class NinePatchAaptProcessorTestUtils {
/**
* Signature of a PNG file.
*/
public static final byte[] SIGNATURE = new byte[]{
(byte) 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
/**
* Returns the lastest build tools that's at least the passed version.
* @param fullRevision the minimum required build tools version.
* @return the latest build tools.
* @throws RuntimeException if the latest build tools is older than fullRevision.
*/
static File getAapt(FullRevision fullRevision) {
ILogger logger = new StdLogger(StdLogger.Level.VERBOSE);
SdkManager sdkManager = SdkManager.createManager(getSdkDir().getAbsolutePath(), logger);
assert sdkManager != null;
BuildToolInfo buildToolInfo = sdkManager.getLatestBuildTool();
if (buildToolInfo == null || buildToolInfo.getRevision().compareTo(fullRevision) < 0) {
throw new RuntimeException("Test requires build-tools " + fullRevision.toShortString());
}
return new File(buildToolInfo.getPath(BuildToolInfo.PathId.AAPT));
}
public static void tearDownAndCheck(int cruncherKey, Map<File, File> sourceAndCrunchedFiles,
PngCruncher cruncher, AtomicLong classStartTime)
throws IOException, DataFormatException {
long startTime = System.currentTimeMillis();
try {
cruncher.end(cruncherKey);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(
"waiting for requests completion : " + (System.currentTimeMillis() - startTime));
System.out.println("total time : " + (System.currentTimeMillis() - classStartTime.get()));
System.out.println("Comparing crunched files");
long comparisonStartTime = System.currentTimeMillis();
for (Map.Entry<File, File> sourceAndCrunched : sourceAndCrunchedFiles.entrySet()) {
System.out.println(sourceAndCrunched.getKey().getName());
File crunched = new File(sourceAndCrunched.getKey().getParent(),
sourceAndCrunched.getKey().getName() + getControlFileSuffix());
//copyFile(sourceAndCrunched.getValue(), crunched);
Map<String, Chunk> testedChunks = compareChunks(crunched, sourceAndCrunched.getValue());
try {
compareImageContent(crunched, sourceAndCrunched.getValue(), false);
} catch (Throwable e) {
throw new RuntimeException("Failed with " + testedChunks.get("IHDR"), e);
}
}
System.out.println("Done comparing crunched files " + (System.currentTimeMillis()
- comparisonStartTime));
}
protected static String getControlFileSuffix() {
return ".crunched.aapt";
}
private static void copyFile(File source, File dest)
throws IOException {
FileChannel inputChannel = null;
FileChannel outputChannel = null;
try {
inputChannel = new FileInputStream(source).getChannel();
outputChannel = new FileOutputStream(dest).getChannel();
outputChannel.transferFrom(inputChannel, 0, inputChannel.size());
} finally {
inputChannel.close();
outputChannel.close();
}
}
@NonNull
static File crunchFile(int crunchKey, @NonNull File file, PngCruncher aaptCruncher)
throws PngException, IOException {
File outFile = File.createTempFile("pngWriterTest", ".png");
outFile.deleteOnExit();
try {
aaptCruncher.crunchPng(crunchKey, file, outFile);
} catch (PngException e) {
e.printStackTrace();
throw e;
}
System.out.println("crunch " + file.getPath());
return outFile;
}
private static Map<String, Chunk> compareChunks(@NonNull File original, @NonNull File tested)
throws
IOException, DataFormatException {
Map<String, Chunk> originalChunks = readChunks(original);
Map<String, Chunk> testedChunks = readChunks(tested);
compareChunk(originalChunks, testedChunks, "IHDR");
compareChunk(originalChunks, testedChunks, "npLb");
compareChunk(originalChunks, testedChunks, "npTc");
return testedChunks;
}
private static void compareChunk(
@NonNull Map<String, Chunk> originalChunks,
@NonNull Map<String, Chunk> testedChunks,
@NonNull String chunkType) {
assertEquals(originalChunks.get(chunkType), testedChunks.get(chunkType));
}
public static Collection<Object[]> getNinePatches() {
File pngFolder = getPngFolder();
File ninePatchFolder = new File(pngFolder, "ninepatch");
File[] files = ninePatchFolder.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
return file.getPath().endsWith(SdkConstants.DOT_9PNG);
}
});
if (files != null) {
ImmutableList.Builder<Object[]> params = ImmutableList.builder();
for (File file : files) {
params.add(new Object[]{file, file.getName()});
}
return params.build();
}
return ImmutableList.of();
}
protected static void compareImageContent(@NonNull File originalFile, @NonNull File createdFile,
boolean is9Patch)
throws IOException {
BufferedImage originalImage = ImageIO.read(originalFile);
BufferedImage createdImage = ImageIO.read(createdFile);
int originalWidth = originalImage.getWidth();
int originalHeight = originalImage.getHeight();
int createdWidth = createdImage.getWidth();
int createdHeight = createdImage.getHeight();
// compare sizes taking into account if the image is a 9-patch
// in which case the original is bigger by 2 since it has the patch area still.
Assert.assertEquals(originalWidth, createdWidth + (is9Patch ? 2 : 0));
Assert.assertEquals(originalHeight, createdHeight + (is9Patch ? 2 : 0));
// get the file content
// always use the created Size. And for the original image, if 9-patch, just take
// the image minus the 1-pixel border all around.
int[] originalContent = new int[createdWidth * createdHeight];
if (is9Patch) {
originalImage
.getRGB(1, 1, createdWidth, createdHeight, originalContent, 0, createdWidth);
} else {
originalImage
.getRGB(0, 0, createdWidth, createdHeight, originalContent, 0, createdWidth);
}
int[] createdContent = new int[createdWidth * createdHeight];
createdImage.getRGB(0, 0, createdWidth, createdHeight, createdContent, 0, createdWidth);
for (int y = 0; y < createdHeight; y++) {
for (int x = 0; x < createdWidth; x++) {
int originalRGBA = originalContent[y * createdWidth + x];
int createdRGBA = createdContent[y * createdWidth + x];
Assert.assertEquals(
String.format("%dx%d: 0x%08x : 0x%08x", x, y, originalRGBA, createdRGBA),
originalRGBA,
createdRGBA);
}
}
}
@NonNull
protected static Map<String, Chunk> readChunks(@NonNull File file) throws IOException {
Map<String, Chunk> chunks = Maps.newHashMap();
byte[] fileBuffer = Files.toByteArray(file);
ByteBuffer buffer = ByteBuffer.wrap(fileBuffer);
byte[] sig = new byte[8];
buffer.get(sig);
assertTrue(Arrays.equals(sig, SIGNATURE));
byte[] data, type;
int len;
int crc32;
while (buffer.hasRemaining()) {
len = buffer.getInt();
type = new byte[4];
buffer.get(type);
data = new byte[len];
buffer.get(data);
// crc
crc32 = buffer.getInt();
Chunk chunk = new Chunk(type, data, crc32);
chunks.put(chunk.getTypeAsString(), chunk);
}
return chunks;
}
/**
* Returns the SDK folder as built from the Android source tree.
*
* @return the SDK
*/
@NonNull
protected static File getSdkDir() {
String androidHome = System.getenv("ANDROID_HOME");
if (androidHome != null) {
File f = new File(androidHome);
if (f.isDirectory()) {
return f;
}
}
throw new IllegalStateException("SDK not defined with ANDROID_HOME");
}
@NonNull
protected static File getFile(@NonNull String name) {
return new File(getPngFolder(), name);
}
@NonNull
protected static File getPngFolder() {
File folder = TestUtils.getRoot("png");
assertTrue(folder.isDirectory());
return folder;
}
}