/*
* Copyright (C) 2016 Patrick Favre-Bulle
*
* 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 at.favre.tools.dconvert.util;
import at.favre.tools.dconvert.converters.scaling.ScaleAlgorithm;
import at.favre.tools.dconvert.converters.scaling.ThumbnailnatorProgressiveAlgorithm;
import at.favre.tools.dconvert.exceptions.NinePatchException;
import java.awt.*;
import java.awt.image.BufferedImage;
/**
* Scales 9-patches correctly, keeping the 1px border intact.
* <p>
* Adapted from <a href="https://github.com/redwarp/9-Patch-Resizer/blob/develop/src/net/redwarp/tool/resizer/worker/ImageScaler.java">Github</a>
*
* @author Redwarp, pfavre
*/
public class NinePatchScaler {
private ScaleAlgorithm algorithm;
private ScaleAlgorithm borderScalerAlgorithm = new ThumbnailnatorProgressiveAlgorithm(RenderingHints.VALUE_INTERPOLATION_BILINEAR);
public BufferedImage scale(BufferedImage inputImage, Dimension dimensions, ScaleAlgorithm algorithm) throws NinePatchException {
this.algorithm = algorithm;
BufferedImage trimmedImage = this.trim9PBorder(inputImage);
trimmedImage = algorithm.scale(trimmedImage, dimensions.width, dimensions.height);
BufferedImage borderImage;
int w = trimmedImage.getWidth();
int h = trimmedImage.getHeight();
borderImage = this.generateBordersImage(inputImage, w, h);
int[] rgbArray = new int[w * h];
trimmedImage.getRGB(0, 0, w, h, rgbArray, 0, w);
borderImage.setRGB(1, 1, w, h, rgbArray, 0, w);
rgbArray = null;
return borderImage;
}
private BufferedImage trim9PBorder(BufferedImage inputImage) {
BufferedImage trimedImage = new BufferedImage(inputImage.getWidth() - 2, inputImage.getHeight() - 2, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = trimedImage.createGraphics();
g.drawImage(inputImage, 0, 0, trimedImage.getWidth(), trimedImage.getHeight(), 1, 1, inputImage.getWidth() - 1, inputImage.getHeight() - 1, null);
g.dispose();
return trimedImage;
}
private void enforceBorderColors(BufferedImage inputImage) {
Graphics2D g = inputImage.createGraphics();
g.setBackground(new Color(0, 0, 0, 0));
g.clearRect(1, 1, inputImage.getWidth() - 2, inputImage.getHeight() - 2);
g.dispose();
int w = inputImage.getWidth();
int h = inputImage.getHeight();
int[] rgb = new int[w * h];
inputImage.getRGB(0, 0, w, h, rgb, 0, w);
for (int i = 0; i < rgb.length; i++) {
if ((0xff000000 & rgb[i]) != 0) {
rgb[i] = 0xff000000;
}
}
inputImage.setRGB(0, 0, w, h, rgb, 0, w);
inputImage.setRGB(0, 0, 0x0);
inputImage.setRGB(0, h - 1, 0x0);
inputImage.setRGB(w - 1, h - 1, 0x0);
inputImage.setRGB(w - 1, 0, 0x0);
}
private BufferedImage generateBordersImage(BufferedImage source, int trimedWidth, int trimedHeight) throws NinePatchException {
BufferedImage finalBorder = new BufferedImage(trimedWidth + 2, trimedHeight + 2, BufferedImage.TYPE_INT_ARGB);
int cutW = source.getWidth() - 2;
int cutH = source.getHeight() - 2;
// left border
BufferedImage leftBorder = new BufferedImage(1, cutH, BufferedImage.TYPE_INT_ARGB);
leftBorder.setRGB(0, 0, 1, cutH, source.getRGB(0, 1, 1, cutH, null, 0, 1), 0, 1);
this.verifyBorderImage(leftBorder);
leftBorder = this.resizeBorder(leftBorder, 1, trimedHeight);
finalBorder.setRGB(0, 1, 1, trimedHeight, leftBorder.getRGB(0, 0, 1, trimedHeight, null, 0, 1), 0, 1);
// right border
BufferedImage rightBorder = new BufferedImage(1, cutH, BufferedImage.TYPE_INT_ARGB);
rightBorder.setRGB(0, 0, 1, cutH, source.getRGB(cutW + 1, 1, 1, cutH, null, 0, 1), 0, 1);
this.verifyBorderImage(rightBorder);
rightBorder = this.resizeBorder(rightBorder, 1, trimedHeight);
finalBorder.setRGB(trimedWidth + 1, 1, 1, trimedHeight, rightBorder.getRGB(0, 0, 1, trimedHeight, null, 0, 1), 0, 1);
// top border
BufferedImage topBorder = new BufferedImage(cutW, 1, BufferedImage.TYPE_INT_ARGB);
topBorder.setRGB(0, 0, cutW, 1, source.getRGB(1, 0, cutW, 1, null, 0, cutW), 0, cutW);
this.verifyBorderImage(topBorder);
topBorder = this.resizeBorder(topBorder, trimedWidth, 1);
finalBorder.setRGB(1, 0, trimedWidth, 1, topBorder.getRGB(0, 0, trimedWidth, 1, null, 0, trimedWidth), 0, trimedWidth);
// bottom border
BufferedImage bottomBorder = new BufferedImage(cutW, 1, BufferedImage.TYPE_INT_ARGB);
bottomBorder.setRGB(0, 0, cutW, 1, source.getRGB(1, cutH + 1, cutW, 1, null, 0, cutW), 0, cutW);
this.verifyBorderImage(bottomBorder);
bottomBorder = this.resizeBorder(bottomBorder, trimedWidth, 1);
finalBorder.setRGB(1, trimedHeight + 1, trimedWidth, 1, bottomBorder.getRGB(0, 0, trimedWidth, 1, null, 0, trimedWidth), 0, trimedWidth);
return finalBorder;
}
private BufferedImage resizeBorder(final BufferedImage border, int targetWidth, int targetHeight) {
if (targetWidth > border.getWidth()
|| targetHeight > border.getHeight()) {
BufferedImage endImage = borderScalerAlgorithm.scale(border, targetWidth, targetHeight);
this.enforceBorderColors(endImage);
return endImage;
}
int w = border.getWidth();
int h = border.getHeight();
int[] data = border.getRGB(0, 0, w, h, null, 0, w);
int[] newData = new int[targetWidth * targetHeight];
float widthRatio = (float) Math.max(targetWidth - 1, 1)
/ (float) Math.max(w - 1, 1);
float heightRatio = (float) Math.max(targetHeight - 1, 1)
/ (float) Math.max(h - 1, 1);
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
if ((0xff000000 & data[y * w + x]) != 0) {
int newX = Math.min(Math.round(x * widthRatio), targetWidth - 1);
int newY = Math.min(Math.round(y * heightRatio), targetHeight - 1);
newData[newY * targetWidth + newX] = data[y * w + x];
}
}
}
BufferedImage img = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_ARGB);
img.setRGB(0, 0, targetWidth, targetHeight, newData, 0, targetWidth);
return img;
}
private void verifyBorderImage(BufferedImage border)
throws NinePatchException {
int[] rgb = border.getRGB(0, 0, border.getWidth(), border.getHeight(),
null, 0, border.getWidth());
for (int i = 0; i < rgb.length; i++) {
if ((0xff000000 & rgb[i]) != 0) {
if (rgb[i] != 0xff000000 && rgb[i] != 0xffff0000) {
throw new NinePatchException();
}
}
}
}
}