package at.favre.tools.dconvert.converters.scaling;
import at.favre.tools.dconvert.arg.Arguments;
import at.favre.tools.dconvert.arg.EScalingAlgorithm;
import at.favre.tools.dconvert.arg.ImageType;
import at.favre.tools.dconvert.util.LoadedImage;
import at.favre.tools.dconvert.util.MiscUtil;
import at.favre.tools.dconvert.util.NinePatchScaler;
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.FileImageOutputStream;
import javax.imageio.stream.ImageOutputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.List;
import java.util.stream.Collectors;
/**
* Handles scaling and writing/compression images to disk
*/
public class ImageHandler {
private static final Color DEFAULT_COLOR = Color.white;
public static final boolean TEST_MODE = false;
public static final ConvolveOp OP_ANTIALIAS = new ConvolveOp(new Kernel(3, 3, new float[]{.0f, .08f, .0f, .08f, .68f, .08f, .0f, .08f, .0f}), ConvolveOp.EDGE_NO_OP, null);
public static Map<ScaleAlgorithm, Long> traceMap = new HashMap<>();
private Arguments args;
public ImageHandler(Arguments args) {
this.args = args;
}
public List<File> saveToFile(File targetFile, LoadedImage imageData, Dimension targetDimension, boolean isNinePatch) throws Exception {
List<File> files = new ArrayList<>(2);
List<ImageType.ECompression> compressionList = Arguments.getOutCompressionForType(args.outputCompressionMode, Arguments.getImageType(imageData.getSourceFile()));
for (ImageType.ECompression compression : compressionList) {
File imageFile = new File(targetFile.getAbsolutePath() + "." + compression.extension);
if (imageFile.exists() && args.skipExistingFiles) {
break;
}
List<ScaleAlgorithm> algorithms = getScaleAlgorithm(getScalingAlgorithm(getScalingType(imageData, targetDimension)), getScalingType(imageData, targetDimension));
for (ScaleAlgorithm scaleAlgorithm : algorithms) {
if (!traceMap.containsKey(scaleAlgorithm)) {
traceMap.put(scaleAlgorithm, 0L);
}
BufferedImage scaledImage;
if (isNinePatch && compression == ImageType.ECompression.PNG) {
scaledImage = new NinePatchScaler().scale(imageData.getImage(), targetDimension, getAsScalingAlgorithm(scaleAlgorithm, compression));
} else {
long startNanos = System.nanoTime();
scaledImage = scale(scaleAlgorithm, imageData.getImage(), targetDimension.width, targetDimension.height, compression, DEFAULT_COLOR);
traceMap.put(scaleAlgorithm, traceMap.get(scaleAlgorithm) + (System.nanoTime() - startNanos));
}
File fileToSave = imageFile;
if (algorithms.size() > 1) {
fileToSave = new File(imageFile.getParentFile(), MiscUtil.getFileNameWithoutExtension(imageFile) + "." + scaleAlgorithm.toString() + "." + MiscUtil.getFileExtension(imageFile));
}
if (compression == ImageType.ECompression.JPG) {
compressJpeg(scaledImage, null, args.compressionQuality, fileToSave);
} else {
ImageIO.write(scaledImage, compression.name().toLowerCase(), fileToSave);
}
scaledImage.flush();
files.add(imageFile);
}
}
return files;
}
private void compressJpeg(BufferedImage bufferedImage, CompoundDirectory exif, float quality, File targetFile) throws IOException {
ImageWriter jpgWriter = ImageIO.getImageWritersByFormatName("jpg").next();
ImageWriteParam jpgWriteParam = jpgWriter.getDefaultWriteParam();
jpgWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
jpgWriteParam.setCompressionQuality(quality);
ImageWriter writer = null;
try (ImageOutputStream outputStream = new FileImageOutputStream(targetFile)) {
writer = ImageIO.getImageWritersByFormatName("jpg").next();
writer.setOutput(outputStream);
writer.write(null, new IIOImage(bufferedImage, null, null), jpgWriteParam);
} finally {
if (writer != null) writer.dispose();
}
}
private EScalingAlgorithm getScalingAlgorithm(EScalingAlgorithm.Type type) {
return type == EScalingAlgorithm.Type.UPSCALING ? args.upScalingAlgorithm : args.downScalingAlgorithm;
}
private EScalingAlgorithm.Type getScalingType(LoadedImage imageData, Dimension targetDimension) {
long targetSize = targetDimension.height * targetDimension.width;
long sourceSize = imageData.getImage().getHeight() * imageData.getImage().getWidth();
return targetSize >= sourceSize ? EScalingAlgorithm.Type.UPSCALING : EScalingAlgorithm.Type.DOWNSCALING;
}
private List<ScaleAlgorithm> getScaleAlgorithm(EScalingAlgorithm algorithm, EScalingAlgorithm.Type type) {
if (TEST_MODE) {
return EScalingAlgorithm.getAllEnabled().stream().filter(eScalingAlgorithm -> eScalingAlgorithm.getSupportedForType().contains(type)).map(EScalingAlgorithm::getImplementation).collect(Collectors.toList());
} else {
return Collections.singletonList(algorithm.getImplementation());
}
}
private BufferedImage scale(ScaleAlgorithm scaleAlgorithm, BufferedImage imageToScale, int dWidth, int dHeight, ImageType.ECompression compression, Color background) {
BufferedImage scaledImage;
if (dWidth == imageToScale.getWidth() && dHeight == imageToScale.getHeight()) {
scaledImage = imageToScale;
} else {
scaledImage = scaleAlgorithm.scale(imageToScale, dWidth, dHeight);
}
if (!compression.hasTransparency) {
BufferedImage convertedImg = new BufferedImage(scaledImage.getWidth(), scaledImage.getHeight(), BufferedImage.TYPE_INT_RGB);
convertedImg.getGraphics().drawImage(scaledImage, 0, 0, background, null);
scaledImage = convertedImg;
}
if (args.enableAntiAliasing) {
scaledImage = OP_ANTIALIAS.filter(scaledImage, null);
}
return scaledImage;
}
private ScaleAlgorithm getAsScalingAlgorithm(final ScaleAlgorithm algorithm, ImageType.ECompression compression) {
return (imageToScale, dWidth, dHeight) -> ImageHandler.this.scale(algorithm, imageToScale, dWidth, dHeight, compression, DEFAULT_COLOR);
}
}