com.android.builder.png.PngProcessor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of builder Show documentation
Show all versions of builder Show documentation
Library to build Android applications with support for Amazon APIs.
/*
* 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 com.google.common.base.Preconditions.checkNotNull;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import com.google.common.io.Files;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.Deflater;
import javax.imageio.ImageIO;
/**
* a PngProcessor.
*
* It reads a png file and write another png file that's been optimized
* and processed in case of a 9-patch.
*/
public class PngProcessor {
private static final int COLOR_WHITE = 0xFFFFFFFF;
private static final int COLOR_TICK = 0xFF000000;
private static final int COLOR_LAYOUT_BOUNDS_TICK = 0xFFFF0000;
private static final int PNG_9PATCH_NO_COLOR = 0x00000001;
private static final int PNG_9PATCH_TRANSPARENT_COLOR = 0x00000000;
@NonNull
private final File mFile;
private Chunk mIhdr;
private Chunk mIdat;
private List mOtherChunks = Lists.newArrayList();
/**
* Processes a given png and writes the resulting png file.
* @param from the input file
* @param to the destination file
* @throws IOException
* @throws NinePatchException
*/
public static void process(@NonNull File from, @NonNull File to)
throws IOException, NinePatchException {
PngProcessor processor = new PngProcessor(from);
processor.read();
if (!processor.is9Patch() && processor.size() >= from.length()) {
Files.copy(from, to);
return;
}
PngWriter writer = new PngWriter(to);
writer.setIhdr(processor.getIhdr())
.setChunks(processor.getOtherChunks())
.setChunk(processor.getIdat());
writer.write();
}
public static void clearCache() {
ByteUtils.Cache.getCache().clear();
}
@VisibleForTesting
PngProcessor(@NonNull File file) {
checkNotNull(file);
mFile = file;
}
@VisibleForTesting
@NonNull
Chunk getIhdr() {
return mIhdr;
}
@VisibleForTesting
@NonNull
List getOtherChunks() {
return mOtherChunks;
}
@VisibleForTesting
@NonNull
Chunk getIdat() {
return mIdat;
}
@VisibleForTesting
void read() throws IOException, NinePatchException {
BufferedImage image = ImageIO.read(mFile);
processImageContent(image);
}
private void addChunk(@NonNull Chunk chunk) {
mOtherChunks.add(chunk);
}
/**
* Returns the size of the generated png.
*/
long size() {
long size = PngWriter.SIGNATURE.length;
size += mIhdr.size();
size += mIdat.size();
for (Chunk chunk : mOtherChunks) {
size += chunk.size();
}
return size;
}
private void processImageContent(@NonNull BufferedImage image) throws NinePatchException,
IOException {
int width = image.getWidth();
int height = image.getHeight();
int[] content = new int[width * height];
image.getRGB(0, 0, width, height, content, 0, width);
int startX = 0;
int startY = 0;
int endX = width;
int endY = height;
if (is9Patch()) {
startX = 1;
startY = 1;
endX--;
endY--;
processBorder(content, width, height);
}
ColorType colorType = createImage(content, width, startX, endX, startY, endY, is9Patch());
mIhdr = computeIhdr(endX - startX, endY - startY, (byte) 8, colorType);
}
private void writeIDat(byte[] data) throws IOException {
// create a growing buffer for the result.
ByteArrayOutputStream bos = new ByteArrayOutputStream(data.length);
Deflater deflater = new Deflater(Deflater.DEFLATED);
deflater.setInput(data);
deflater.finish();
// temp buffer for compressed data.
byte[] tmpBuffer = new byte[1024];
while (!deflater.finished()) {
int compressedLen = deflater.deflate(tmpBuffer);
bos.write(tmpBuffer, 0, compressedLen);
}
bos.close();
byte[] compressedData = bos.toByteArray();
mIdat = new Chunk(PngWriter.IDAT, compressedData);
}
/**
* Creates the PNG image buffer to be encoded in IDAT.
*
* This figures out the smallest way to encode it, and creates the IDAT chunk (and other needed
* chunks like PLTE).
*
* @param content the image array in ARGB.
* @param scanline the scanline value for the image buffer
* @param startX the startX of the image we want to create from the buffer
* @param endX the endX of the image we want to create from the buffer
* @param startY the startY of the image we want to create from the buffer
* @param endY the endY of the image we want to create from the buffer
* @param is9Patch whether the image is a nine-patch
*/
private ColorType createImage(@NonNull int[] content, int scanline,
int startX, int endX, int startY, int endY, boolean is9Patch) throws IOException {
int[] paletteColors = new int[256];
int paletteColorCount = 0;
int grayscaleTolerance = -1;
int maxGrayDeviation = 0;
boolean isOpaque = true;
boolean isPalette = true;
boolean isGrayscale = true;
int width = endX - startX;
int height = endY - startY;
// RGBA buffer, in case it's the best one.
int rgbaLen = (1 + width * 4) * height;
ByteBuffer rgbaBufer = ByteBuffer.allocate(rgbaLen);
// Store palette data optimistically in case we use palette mode.
// Better than regoing through content after.
int indexedLen = (1 + width) * height;
byte[] indexedContent = new byte[indexedLen];
int indexedContentIndex = 0;
// Scan the entire image and determine if:
// 1. Every pixel has R == G == B (grayscale)
// 2. Every pixel has A == 255 (opaque)
// 3. There are no more than 256 distinct RGBA colors
for (int y = startY ; y < endY ; y++) {
rgbaBufer.put((byte) 0);
indexedContent[indexedContentIndex++] = 0;
for (int x = startX ; x < endX ; x++) {
int argb = content[(scanline * y) + x];
int aa = argb >>> 24;
int rr = (argb >> 16) & 0x000000FF;
int gg = (argb >> 8) & 0x000000FF;
int bb = argb & 0x000000FF;
int odev = maxGrayDeviation;
maxGrayDeviation = Math.max(Math.abs(rr - gg), maxGrayDeviation);
maxGrayDeviation = Math.max(Math.abs(gg - bb), maxGrayDeviation);
maxGrayDeviation = Math.max(Math.abs(bb - rr), maxGrayDeviation);
// Check if image is really grayscale
if (isGrayscale && (rr != gg || rr != bb)) {
isGrayscale = false;
}
// Check if image is really opaque
if (isOpaque && aa != 0xff) {
isOpaque = false;
}
// Check if image is really <= 256 colors
if (isPalette) {
int rgba = (argb << 8) | aa;
boolean match = false;
int idx;
for (idx = 0; idx < paletteColorCount; idx++) {
if (paletteColors[idx] == rgba) {
match = true;
break;
}
}
// Write the palette index for the pixel to outRows optimistically
// We might overwrite it later if we decide to encode as gray or
// gray + alpha
indexedContent[indexedContentIndex++] = (byte)idx;
if (!match) {
if (paletteColorCount == 256) {
isPalette = false;
} else {
paletteColors[paletteColorCount++] = rgba;
}
}
}
// write rgba optimistically
rgbaBufer.putInt((argb << 8) | aa);
}
}
boolean hasTransparency = !isOpaque;
int bpp = isOpaque ? 3 : 4;
int paletteSize = width * height + (isOpaque ? 3 : 4) * paletteColorCount;
ColorType colorType;
// Choose the best color type for the image.
// 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel
// 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct combinations
// is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA
// 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is sufficiently
// small, otherwise use COLOR_TYPE_RGB{_ALPHA}
if (isGrayscale) {
if (isOpaque) {
colorType = ColorType.GRAY_SCALE; // 1 byte/pixel
} else {
// Use a simple heuristic to determine whether using a palette will
// save space versus using gray + alpha for each pixel.
// This doesn't take into account chunk overhead, filtering, LZ
// compression, etc.
if (isPalette && paletteSize < 2 * width * height) {
colorType = ColorType.PLTE; // 1 byte/pixel + 4 bytes/color
} else {
colorType = ColorType.GRAY_SCALE_ALPHA; // 2 bytes per pixel
}
}
} else if (isPalette && paletteSize < bpp * width * height) {
colorType = ColorType.PLTE; // 1 byte/pixel + 4 bytes/color
} else {
if (maxGrayDeviation <= grayscaleTolerance) {
colorType = isOpaque ? ColorType.GRAY_SCALE : ColorType.GRAY_SCALE_ALPHA;
} else {
colorType = isOpaque ? ColorType.RGB : ColorType.RGBA;
}
}
// If the image is a 9-patch, we need to preserve it as a ARGB file to make
// sure the pixels will not be pre-dithered/clamped until we decide they are
if (is9Patch && (colorType == ColorType.RGB ||
colorType == ColorType.GRAY_SCALE || colorType == ColorType.PLTE)) {
colorType = ColorType.RGBA;
}
// Perform postprocessing of the image or palette data based on the final
// color type chosen
if (colorType == ColorType.PLTE) {
byte[] rgbPalette = new byte[paletteColorCount * 3];
byte[] alphaPalette = null;
if (hasTransparency) {
alphaPalette = new byte[paletteColorCount];
}
// Create the RGB and alpha palettes
for (int idx = 0; idx < paletteColorCount; idx++) {
int color = paletteColors[idx];
rgbPalette[idx * 3] = (byte) ( color >>> 24);
rgbPalette[idx * 3 + 1] = (byte) ((color >> 16) & 0xFF);
rgbPalette[idx * 3 + 2] = (byte) ((color >> 8) & 0xFF);
if (hasTransparency) {
alphaPalette[idx] = (byte) (color & 0xFF);
}
}
// create chunks.
addChunk(new Chunk(PngWriter.PLTE, rgbPalette));
if (hasTransparency) {
addChunk(new Chunk(PngWriter.TRNS, alphaPalette));
}
// create image data chunk
writeIDat(indexedContent);
} else if (colorType == ColorType.GRAY_SCALE || colorType == ColorType.GRAY_SCALE_ALPHA) {
int grayLen = (1 + width * (1 + (hasTransparency ? 1 : 0))) * height;
byte[] grayContent = new byte[grayLen];
int grayContentIndex = 0;
for (int y = startY ; y < endY ; y++) {
grayContent[grayContentIndex++] = 0;
for (int x = startX ; x < endX ; x++) {
int argb = content[(scanline * y) + x];
int rr = (argb >> 16) & 0x000000FF;
if (isGrayscale) {
grayContent[grayContentIndex++] = (byte) rr;
} else {
int gg = (argb >> 8) & 0x000000FF;
int bb = argb & 0x000000FF;
// convert RGB to Grayscale.
// Ref: http://en.wikipedia.org/wiki/Luma_(video)
grayContent[grayContentIndex++] = (byte) (rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
}
if (hasTransparency) {
int aa = argb >>> 24;
grayContent[grayContentIndex++] = (byte) aa;
}
}
}
// create image data chunk
writeIDat(grayContent);
} else if (colorType == ColorType.RGBA) {
writeIDat(rgbaBufer.array());
} else {
//RGB mode
int rgbLen = (1 + width * 3) * height;
byte[] rgbContent = new byte[rgbLen];
int rgbContentIndex = 0;
for (int y = startY ; y < endY ; y++) {
rgbContent[rgbContentIndex++] = 0;
for (int x = startX ; x < endX ; x++) {
int argb = content[(scanline * y) + x];
rgbContent[rgbContentIndex++] = (byte) ((argb >> 16) & 0x000000FF);
rgbContent[rgbContentIndex++] = (byte) ((argb >> 8) & 0x000000FF);
rgbContent[rgbContentIndex++] = (byte) ( argb & 0x000000FF);
}
}
// create image data chunk
writeIDat(rgbContent);
}
return colorType;
}
/**
* process the border of the image to find 9-patch info
* @param content the content of ARGB
* @param width the width
* @param height the height
* @throws NinePatchException
*/
private void processBorder(int[] content, int width, int height)
throws NinePatchException {
// Validate size...
if (width < 3 || height < 3) {
throw new NinePatchException(mFile, "Image must be at least 3x3 (1x1 without frame) pixels");
}
int i, j;
int[] xDivs = new int[width];
int[] yDivs = new int[height];
int[] colors;
Arrays.fill(xDivs, -1);
Arrays.fill(yDivs, -1);
int numXDivs;
int numYDivs;
byte numColors;
int numRows, numCols;
int top, left, right, bottom;
int paddingLeft, paddingTop, paddingRight, paddingBottom;
boolean transparent = (content[0] & 0xFF000000) == 0;
int colorIndex = 0;
// Validate frame...
if (!transparent && content[0] != 0xFFFFFFFF) {
throw new NinePatchException(mFile,
"Must have one-pixel frame that is either transparent or white");
}
// Find left and right of sizing areas...
AtomicInteger outInt = new AtomicInteger(0);
try {
getHorizontalTicks(
content, 0, width,
transparent, true /*required*/,
xDivs, 0, 1, outInt,
true /*multipleAllowed*/);
numXDivs = outInt.get();
} catch (TickException e) {
throw new NinePatchException(mFile, e, "top");
}
// Find top and bottom of sizing areas...
outInt.set(0);
try {
getVerticalTicks(content, 0, width, height,
transparent, true /*required*/,
yDivs, 0, 1, outInt,
true /*multipleAllowed*/);
numYDivs = outInt.get();
} catch (TickException e) {
throw new NinePatchException(mFile, e, "left");
}
// Find left and right of padding area...
int[] values = new int[2];
try {
getHorizontalTicks(
content, width * (height - 1), width,
transparent, false /*required*/,
values, 0, 1, null,
false /*multipleAllowed*/);
paddingLeft = values[0];
paddingRight = values[1];
values[0] = values[1] = 0;
} catch (TickException e) {
throw new NinePatchException(mFile, e, "bottom");
}
// Find top and bottom of padding area...
try {
getVerticalTicks(
content, width - 1, width, height,
transparent, false /*required*/,
values, 0, 1, null,
false /*multipleAllowed*/);
paddingTop = values[0];
paddingBottom = values[1];
} catch (TickException e) {
throw new NinePatchException(mFile, e, "right");
}
try {
// Find left and right of layout padding...
getHorizontalLayoutBoundsTicks(content, width * (height - 1), width,
transparent, false, values);
} catch (TickException e) {
throw new NinePatchException(mFile, e, "bottom");
}
int[] values2 = new int[2];
try {
getVerticalLayoutBoundsTicks(content, width - 1, width, height,
transparent, false, values2);
} catch (TickException e) {
throw new NinePatchException(mFile, e, "right");
}
LayoutBoundChunkBuilder layoutBoundChunkBuilder = null;
if (values[0] != 0 || values[1] != 0 || values2[0] != 0 || values2[1] != 0) {
layoutBoundChunkBuilder = new LayoutBoundChunkBuilder(
values[0], values2[0], values[1], values2[1]);
}
// If padding is not yet specified, take values from size.
if (paddingLeft < 0) {
paddingLeft = xDivs[0];
paddingRight = width - 2 - xDivs[1];
} else {
// Adjust value to be correct!
paddingRight = width - 2 - paddingRight;
}
if (paddingTop < 0) {
paddingTop = yDivs[0];
paddingBottom = height - 2 - yDivs[1];
} else {
// Adjust value to be correct!
paddingBottom = height - 2 - paddingBottom;
}
// Remove frame from image.
width -= 2;
height -= 2;
// Figure out the number of rows and columns in the N-patch
numCols = numXDivs + 1;
if (xDivs[0] == 0) { // Column 1 is strechable
numCols--;
}
if (xDivs[numXDivs - 1] == width) {
numCols--;
}
numRows = numYDivs + 1;
if (yDivs[0] == 0) { // Row 1 is strechable
numRows--;
}
if (yDivs[numYDivs - 1] == height) {
numRows--;
}
// Make sure the amount of rows and columns will fit in the number of
// colors we can use in the 9-patch format.
if (numRows * numCols > 0x7F) {
throw new NinePatchException(mFile, "Too many rows and columns in 9-patch perimeter");
}
numColors = (byte) (numRows * numCols);
colors = new int[numColors];
// Fill in color information for each patch.
int c;
top = 0;
// The first row always starts with the top being at y=0 and the bottom
// being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case
// the first row is stretchable along the Y axis, otherwise it is fixed.
// The last row always ends with the bottom being bitmap.height and the top
// being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
// yDivs[numYDivs-1]. In the former case the last row is stretchable along
// the Y axis, otherwise it is fixed.
//
// The first and last columns are similarly treated with respect to the X
// axis.
//
// The above is to help explain some of the special casing that goes on the
// code below.
// The initial yDiv and whether the first row is considered stretchable or
// not depends on whether yDiv[0] was zero or not.
for (j = (yDivs[0] == 0 ? 1 : 0);
j <= numYDivs && top < height;
j++) {
if (j == numYDivs) {
bottom = height;
} else {
bottom = yDivs[j];
}
left = 0;
// The initial xDiv and whether the first column is considered
// stretchable or not depends on whether xDiv[0] was zero or not.
for (i = xDivs[0] == 0 ? 1 : 0;
i <= numXDivs && left < width;
i++) {
if (i == numXDivs) {
right = width;
} else {
right = xDivs[i];
}
c = getColor(content, width + 2, left, top, right - 1, bottom - 1);
colors[colorIndex++] = c;
left = right;
}
top = bottom;
}
// Create the chunks.
NinePatchChunkBuilder ninePatchChunkBuilder = new NinePatchChunkBuilder(
xDivs, numXDivs, yDivs, numYDivs, colors,
paddingLeft, paddingRight, paddingTop, paddingBottom);
addChunk(ninePatchChunkBuilder.getChunk());
if (layoutBoundChunkBuilder != null) {
addChunk(layoutBoundChunkBuilder.getChunk());
}
}
/**
* returns a color. the top/left/right/bottom coordinate are in a subframe of content, starting
* in (1,1).
* @param content the image buffer
* @param width the width of the image buffer
* @param left left coordinate.
* @param top top coordinate.
* @param right right coordinate.
* @param bottom bottom coordinate.
* @return a color.
*/
static int getColor(@NonNull int[] content, int width,
int left, int top, int right, int bottom) {
int color = content[(top + 1) * width + left + 1];
int alpha = color & 0xFF000000;
if (left > right || top > bottom) {
return PNG_9PATCH_TRANSPARENT_COLOR;
}
while (top <= bottom) {
for (int i = left; i <= right; i++) {
int c = content[(top + 1) * width + i + 1];
if (alpha == 0) {
if ((c & 0xFF000000) != 0) {
return PNG_9PATCH_NO_COLOR;
}
} else if (c != color) {
return PNG_9PATCH_NO_COLOR;
}
}
top++;
}
if (alpha == 0) {
return PNG_9PATCH_TRANSPARENT_COLOR;
}
return color;
}
private static enum TickType {
NONE, TICK, LAYOUT_BOUNDS, BOTH
}
@NonNull
private static TickType getTickType(int color, boolean transparent) throws TickException {
int alpha = color >>> 24;
if (transparent) {
if (alpha == 0) {
return TickType.NONE;
}
if (color == COLOR_LAYOUT_BOUNDS_TICK) {
return TickType.LAYOUT_BOUNDS;
}
if (color == COLOR_TICK) {
return TickType.TICK;
}
// Error cases
if (alpha != 0xFF) {
throw TickException.createWithColor(
"Frame pixels must be either solid or transparent (not intermediate alphas)",
color);
}
if ((color & 0x00FFFFFF) != 0) {
throw TickException.createWithColor("Ticks in transparent frame must be black or red",
color);
}
return TickType.TICK;
}
if (alpha != 0xFF) {
throw TickException.createWithColor("White frame must be a solid color (no alpha)",
color);
}
if (color == COLOR_WHITE) {
return TickType.NONE;
}
if (color == COLOR_TICK) {
return TickType.TICK;
}
if (color == COLOR_LAYOUT_BOUNDS_TICK) {
return TickType.LAYOUT_BOUNDS;
}
if ((color & 0x00FFFFFF) != 0) {
throw TickException.createWithColor("Ticks in transparent frame must be black or red",
color);
}
return TickType.TICK;
}
private static enum Tick {
START, INSIDE_1, OUTSIDE_1
}
private static void getHorizontalTicks(
@NonNull int[] content, int offset, int width,
boolean transparent, boolean required,
@NonNull int[] divs, int left, int right,
@Nullable AtomicInteger outDivs, boolean multipleAllowed) throws TickException {
int i;
divs[left] = divs[right] = -1;
Tick state = Tick.START;
boolean found = false;
for (i = 1; i < width - 1; i++) {
TickType tickType;
try {
tickType = getTickType(content[offset + i], transparent);
} catch (TickException e) {
throw new TickException(e, i);
}
if (TickType.TICK == tickType) {
if (state == Tick.START ||
(state == Tick.OUTSIDE_1 && multipleAllowed)) {
divs[left] = i - 1;
divs[right] = width - 2;
found = true;
if (outDivs != null) {
outDivs.addAndGet(2);
}
state = Tick.INSIDE_1;
} else if (state == Tick.OUTSIDE_1) {
throw new TickException("Can't have more than one marked region along edge");
}
} else {
if (state == Tick.INSIDE_1) {
// We're done with this div. Move on to the next.
divs[right] = i - 1;
right += 2;
left += 2;
state = Tick.OUTSIDE_1;
}
}
}
if (required && !found) {
throw new TickException("No marked region found along edge");
}
}
private static void getVerticalTicks(
@NonNull int[] content, int offset, int width, int height,
boolean transparent, boolean required,
@NonNull int[] divs, int top, int bottom,
@Nullable AtomicInteger outDivs, boolean multipleAllowed) throws TickException {
int i;
divs[top] = divs[bottom] = -1;
Tick state = Tick.START;
boolean found = false;
for (i = 1; i < height - 1; i++) {
TickType tickType;
try {
tickType = getTickType(content[offset + width * i], transparent);
} catch (TickException e) {
throw new TickException(e, i);
}
if (TickType.TICK == tickType) {
if (state == Tick.START ||
(state == Tick.OUTSIDE_1 && multipleAllowed)) {
divs[top] = i - 1;
divs[bottom] = height - 2;
found = true;
if (outDivs != null) {
outDivs.addAndGet(2);
}
state = Tick.INSIDE_1;
} else if (state == Tick.OUTSIDE_1) {
throw new TickException("Can't have more than one marked region along edge");
}
} else {
if (state == Tick.INSIDE_1) {
// We're done with this div. Move on to the next.
divs[bottom] = i - 1;
top += 2;
bottom += 2;
state = Tick.OUTSIDE_1;
}
}
}
if (required && !found) {
throw new TickException("No marked region found along edge");
}
}
private static void getHorizontalLayoutBoundsTicks(
@NonNull int[] content, int offset, int width, boolean transparent, boolean required,
@NonNull int[] outValues) throws TickException {
int i;
outValues[0] = outValues[1] = 0;
// Look for left tick
if (TickType.LAYOUT_BOUNDS == getTickType(content[offset + 1], transparent)) {
// Starting with a layout padding tick
i = 1;
while (i < width - 1) {
outValues[0]++;
i++;
TickType tick = getTickType(content[offset + i], transparent);
if (tick != TickType.LAYOUT_BOUNDS) {
break;
}
}
}
// Look for right tick
if (TickType.LAYOUT_BOUNDS == getTickType(content[offset + (width - 2)], transparent)) {
// Ending with a layout padding tick
i = width - 2;
while (i > 1) {
outValues[1]++;
i--;
TickType tick = getTickType(content[offset + i], transparent);
if (tick != TickType.LAYOUT_BOUNDS) {
break;
}
}
}
}
private static void getVerticalLayoutBoundsTicks(
@NonNull int[] content, int offset, int width, int height,
boolean transparent, boolean required,
@NonNull int[] outValues) throws TickException {
int i;
outValues[0] = outValues[1] = 0;
// Look for top tick
if (TickType.LAYOUT_BOUNDS == getTickType(content[offset + width], transparent)) {
// Starting with a layout padding tick
i = 1;
while (i < height - 1) {
outValues[0]++;
i++;
TickType tick = getTickType(content[offset + width * i], transparent);
if (tick != TickType.LAYOUT_BOUNDS) {
break;
}
}
}
// Look for bottom tick
if (TickType.LAYOUT_BOUNDS == getTickType(content[offset + width * (height - 2)],
transparent)) {
// Ending with a layout padding tick
i = height - 2;
while (i > 1) {
outValues[1]++;
i--;
TickType tick = getTickType(content[offset + width * i], transparent);
if (tick != TickType.LAYOUT_BOUNDS) {
break;
}
}
}
}
@VisibleForTesting
Chunk computeIhdr(int width, int height, byte bitDepth, @NonNull ColorType colorType) {
byte[] buffer = new byte[13];
ByteUtils utils = ByteUtils.Cache.get();
System.arraycopy(utils.getIntAsArray(width), 0, buffer, 0, 4);
System.arraycopy(utils.getIntAsArray(height), 0, buffer, 4, 4);
buffer[8] = bitDepth;
buffer[9] = colorType.getFlag();
buffer[10] = 0; // compressionMethod
buffer[11] = 0; // filterMethod;
buffer[12] = 0; // interlaceMethod
return new Chunk(PngWriter.IHDR, buffer);
}
boolean is9Patch() {
return mFile.getPath().endsWith(SdkConstants.DOT_9PNG);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy