/*
* ******************************************************************************
* * Copyright 2015 See AUTHORS file.
* *
* * 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.uwsoft.editor.proxy;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.imageio.ImageIO;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.tools.texturepacker.TexturePacker;
import com.badlogic.gdx.tools.texturepacker.TextureUnpacker;
import com.badlogic.gdx.utils.Array;
import com.kotcrab.vis.ui.util.dialog.DialogUtils;
import com.mortennobel.imagescaling.ResampleOp;
import com.puremvc.patterns.proxy.BaseProxy;
import com.uwsoft.editor.view.stage.Sandbox;
import com.uwsoft.editor.view.ui.widget.ProgressHandler;
import com.uwsoft.editor.Overlap2DFacade;
import com.uwsoft.editor.renderer.data.ProjectInfoVO;
import com.uwsoft.editor.renderer.data.ResolutionEntryVO;
import com.uwsoft.editor.utils.NinePatchUtils;
import com.uwsoft.editor.utils.Overlap2DUtils;
public class ResolutionManager extends BaseProxy {
private static final String TAG = ResolutionManager.class.getCanonicalName();
public static final String NAME = TAG;
public static final String RESOLUTION_LIST_CHANGED = "com.uwsoft.editor.proxy.ResolutionManager" + ".RESOLUTION_LIST_CHANGED";
private static final String EXTENSION_9PATCH = ".9.png";
public String currentResolutionName;
private float currentPercent = 0.0f;
private ProgressHandler handler;
public ResolutionManager() {
super(NAME);
}
public static BufferedImage imageResize(File file, float ratio) {
BufferedImage destinationBufferedImage = null;
try {
BufferedImage sourceBufferedImage = ImageIO.read(file);
if (ratio == 1.0) {
return sourceBufferedImage;
}
// When image has to be resized smaller then 3 pixels we should leave it as is, as to ResampleOP limitations
// But it should also trigger a warning dialog at the and of the import, to notify the user of non resized images.
if (sourceBufferedImage.getWidth() * ratio < 3 || sourceBufferedImage.getHeight() * ratio < 3) {
return null;
}
int newWidth = Math.max(3, Math.round(sourceBufferedImage.getWidth() * ratio));
int newHeight = Math.max(3, Math.round(sourceBufferedImage.getHeight() * ratio));
String name = file.getName();
Integer[] patches = null;
if (name.endsWith(EXTENSION_9PATCH)) {
patches = NinePatchUtils.findPatches(sourceBufferedImage);
sourceBufferedImage = NinePatchUtils.removePatches(sourceBufferedImage);
newWidth = Math.round(sourceBufferedImage.getWidth() * ratio);
newHeight = Math.round(sourceBufferedImage.getHeight() * ratio);
System.out.println(sourceBufferedImage.getWidth());
destinationBufferedImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = destinationBufferedImage.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
g2.drawImage(sourceBufferedImage, 0, 0, newWidth, newHeight, null);
g2.dispose();
} else {
// resize with bilinear filter
ResampleOp resampleOp = new ResampleOp(newWidth, newHeight);
destinationBufferedImage = resampleOp.filter(sourceBufferedImage, null);
}
if (patches != null) {
destinationBufferedImage = NinePatchUtils.convertTo9Patch(destinationBufferedImage, patches, ratio);
}
} catch (IOException ignored) {
}
return destinationBufferedImage;
}
public static float getResolutionRatio(ResolutionEntryVO resolution, ResolutionEntryVO originalResolution) {
float a;
float b;
switch (resolution.base) {
default:
case 0:
a = resolution.width;
b = originalResolution.width;
break;
case 1:
a = resolution.height;
b = originalResolution.height;
break;
}
return a / b;
}
public static int getMinSquareNum(int num) {
// if (num < 1024) return 1024;
// if (num < 2048) return 2048;
// if (num < 4096) return 4096;
return 4096;
}
@Override
public void onRegister() {
super.onRegister();
facade = Overlap2DFacade.getInstance();
}
// private static BufferedImage convertTo9Patch(BufferedImage image) {
// }
public void createNewResolution(ResolutionEntryVO resolutionEntryVO) {
ProjectManager projectManager = facade.retrieveProxy(ProjectManager.NAME);
projectManager.getCurrentProjectInfoVO().resolutions.add(resolutionEntryVO);
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(() -> {
// create new folder structure
String projPath = projectManager.getCurrentProjectPath();
String sourcePath = projPath + "/" + "assets/orig/images";
String targetPath = projPath + "/" + "assets/" + resolutionEntryVO.name + "/images";
createIfNotExist(sourcePath);
createIfNotExist(projPath + "/" + "assets/" + resolutionEntryVO.name + "/pack");
copyTexturesFromTo(sourcePath, targetPath);
int resizeWarnings = resizeTextures(targetPath, resolutionEntryVO);
rePackProjectImages(resolutionEntryVO);
createResizedAnimations(resolutionEntryVO);
changePercentBy(5);
if (resizeWarnings > 0) {
DialogUtils.showOKDialog(Sandbox.getInstance().getUIStage(), "Warning", resizeWarnings + " images were not resized for smaller resolutions due to already small size ( < 3px )");
}
Overlap2DFacade.getInstance().sendNotification(RESOLUTION_LIST_CHANGED);
});
executor.execute(() -> {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
projectManager.saveCurrentProject();
// handler.progressComplete();
});
executor.shutdown();
}
private void changePercentBy(float value) {
currentPercent += value;
//handler.progressChanged(currentPercent);
}
public void createResizedAnimations(ResolutionEntryVO resolution) {
ProjectManager projectManager = facade.retrieveProxy(ProjectManager.NAME);
String currProjectPath = projectManager.getCurrentProjectPath();
// Unpack spine orig
File spineSourceDir = new File(currProjectPath + File.separator + "assets/orig/spine-animations");
if (spineSourceDir.exists()) {
for (File entry : spineSourceDir.listFiles()) {
if (entry.isDirectory()) {
String animName = FilenameUtils.removeExtension(entry.getName());
createResizedSpineAnimation(animName, resolution);
}
}
}
//Unpack sprite orig
File spriteSourceDir = new File(currProjectPath + File.separator + "assets/orig/sprite-animations");
if (spriteSourceDir.exists()) {
for (File entry : spriteSourceDir.listFiles()) {
if (entry.isDirectory()) {
String animName = FilenameUtils.removeExtension(entry.getName());
createResizedSpriteAnimation(animName, resolution);
}
}
}
}
public void createResizedSpriteAnimation(String animName, ResolutionEntryVO resolution) {
ProjectManager projectManager = facade.retrieveProxy(ProjectManager.NAME);
String currProjectPath = projectManager.getCurrentProjectPath();
File animAtlasFile = new File(currProjectPath + File.separator + "assets/orig/sprite-animations/" + animName + "/" + animName + ".atlas");
String tmpPath = currProjectPath + File.separator + "assets/orig/sprite-animations/" + animName + "/tmp";
File tmpFolder = new File(tmpPath);
try {
FileUtils.forceMkdir(new File(currProjectPath + File.separator + "assets/" + resolution.name + "/sprite-animations/"));
FileUtils.forceMkdir(new File(currProjectPath + File.separator + "assets/" + resolution.name + "/spine-animations/" + animName));
String targetPath = currProjectPath + File.separator + "assets/" + resolution.name + "/sprite-animations/" + animName;
File targetFolder = new File(targetPath);
unpackAtlasIntoTmpFolder(animAtlasFile, tmpPath);
resizeImagesTmpDirToResolution(animName, tmpFolder, resolution, targetFolder);
FileUtils.deleteDirectory(tmpFolder);
} catch (IOException e) {
e.printStackTrace();
}
}
public void unpackAtlasIntoTmpFolder(File atlasFile, String tmpDir) {
FileHandle atlasFileHandle = new FileHandle(atlasFile);
TextureAtlas.TextureAtlasData atlasData = new TextureAtlas.TextureAtlasData(atlasFileHandle, atlasFileHandle.parent(), false);
TextureUnpacker unpacker = new TextureUnpacker();
unpacker.splitAtlas(atlasData, tmpDir);
}
public void createResizedSpineAnimation(String animName, ResolutionEntryVO resolution) {
ProjectManager projectManager = facade.retrieveProxy(ProjectManager.NAME);
String currProjectPath = projectManager.getCurrentProjectPath();
File animAtlasFile = new File(currProjectPath + File.separator + "assets/orig/spine-animations/" + animName + "/" + animName + ".atlas");
String tmpPath = currProjectPath + File.separator + "assets/orig/spine-animations/" + animName + "/tmp";
File tmpFolder = new File(tmpPath);
try {
FileUtils.forceMkdir(new File(currProjectPath + File.separator + "assets/" + resolution.name + "/spine-animations/"));
FileUtils.forceMkdir(new File(currProjectPath + File.separator + "assets/" + resolution.name + "/spine-animations/" + animName));
String targetPath = currProjectPath + File.separator + "assets/" + resolution.name + "/spine-animations/" + animName;
File targetFolder = new File(targetPath);
unpackAtlasIntoTmpFolder(animAtlasFile, tmpPath);
resizeImagesTmpDirToResolution(animName, tmpFolder, resolution, targetFolder);
FileUtils.deleteDirectory(tmpFolder);
} catch (IOException e) {
e.printStackTrace();
}
}
public void resizeSpriteAnimationForAllResolutions(String animName, ProjectInfoVO currentProjectInfoVO) {
ProjectManager projectManager = facade.retrieveProxy(ProjectManager.NAME);
String currProjectPath = projectManager.getCurrentProjectPath();
File atlasFile = new File(currProjectPath + File.separator + "assets" + File.separator + "orig" + File.separator + "sprite-animations" + File.separator + animName + File.separator + animName + ".atlas");
String tmpDir = currProjectPath + File.separator + "assets" + File.separator + "orig" + File.separator + "sprite-animations" + File.separator + animName + File.separator + "tmp";
File sourceFolder = new File(tmpDir);
unpackAtlasIntoTmpFolder(atlasFile, tmpDir);
try {
for (ResolutionEntryVO resolutionEntryVO : currentProjectInfoVO.resolutions) {
String spriteAnimationsRoot = currProjectPath + File.separator + "assets" + File.separator + resolutionEntryVO.name + File.separator + "sprite-animations";
FileUtils.forceMkdir(new File(spriteAnimationsRoot));
String targetPath = spriteAnimationsRoot + File.separator + animName;
File targetFolder = new File(targetPath);
resizeImagesTmpDirToResolution(animName, sourceFolder, resolutionEntryVO, targetFolder);
}
FileUtils.deleteDirectory(sourceFolder);
} catch (IOException e) {
e.printStackTrace();
}
}
public void resizeSpineAnimationForAllResolutions(File atlasFile, ProjectInfoVO currentProjectInfoVO) {
String fileNameWithOutExt = FilenameUtils.removeExtension(atlasFile.getName());
ProjectManager projectManager = facade.retrieveProxy(ProjectManager.NAME);
String tmpDir = projectManager.getCurrentProjectPath() + "/assets/orig/spine-animations" + File.separator + fileNameWithOutExt + File.separator + "tmp";
File sourceFolder = new File(tmpDir);
unpackAtlasIntoTmpFolder(atlasFile, tmpDir);
try {
for (ResolutionEntryVO resolutionEntryVO : currentProjectInfoVO.resolutions) {
FileUtils.forceMkdir(new File(projectManager.getCurrentProjectPath() + File.separator +
"assets" + File.separator + resolutionEntryVO.name + File.separator + "spine-animations"));
String targetPath = projectManager.getCurrentProjectPath() + File.separator + "assets" +
File.separator + resolutionEntryVO.name + File.separator + "spine-animations" + File.separator + fileNameWithOutExt;
FileUtils.forceMkdir(new File(targetPath));
File targetFolder = new File(targetPath);
resizeImagesTmpDirToResolution(atlasFile.getName(), sourceFolder, resolutionEntryVO, targetFolder);
}
FileUtils.deleteDirectory(sourceFolder);
} catch (IOException e) {
e.printStackTrace();
}
}
public void rePackProjectImages(ResolutionEntryVO resEntry) {
ProjectManager projectManager = facade.retrieveProxy(ProjectManager.NAME);
TexturePacker.Settings settings = new TexturePacker.Settings();
settings.flattenPaths = true;
// settings.maxHeight = getMinSquareNum(resEntry.height);
// settings.maxWidth = getMinSquareNum(resEntry.height);
settings.maxHeight = Integer.parseInt(projectManager.getCurrentProjectVO().texturepackerHeight);
settings.maxWidth = Integer.parseInt(projectManager.getCurrentProjectVO().texturepackerWidth);
settings.filterMag = Texture.TextureFilter.Linear;
settings.filterMin = Texture.TextureFilter.Linear;
settings.duplicatePadding = projectManager.getCurrentProjectVO().texturepackerDuplicate;
TexturePacker tp = new TexturePacker(settings);
String sourcePath = projectManager.getCurrentProjectPath() + "/assets/" + resEntry.name + "/images";
String outputPath = projectManager.getCurrentProjectPath() + "/assets/" + resEntry.name + "/pack";
FileHandle sourceDir = new FileHandle(sourcePath);
File outputDir = new File(outputPath);
try {
FileUtils.forceMkdir(outputDir);
FileUtils.cleanDirectory(outputDir);
} catch (IOException e) {
e.printStackTrace();
}
for (FileHandle entry : sourceDir.list()) {
String filename = entry.file().getName();
String extension = filename.substring(filename.lastIndexOf(".") + 1, filename.length()).toLowerCase();
if (extension.equals("png")) {
tp.addImage(entry.file());
}
}
tp.pack(outputDir, "pack");
}
private int resizeTextures(String path, ResolutionEntryVO resolution) {
ProjectManager projectManager = facade.retrieveProxy(ProjectManager.NAME);
float ratio = getResolutionRatio(resolution, projectManager.getCurrentProjectInfoVO().originalResolution);
FileHandle targetDir = new FileHandle(path);
FileHandle[] entries = targetDir.list(Overlap2DUtils.PNG_FILTER);
float perResizePercent = 95.0f / entries.length;
int resizeWarnings = 0;
for (FileHandle entry : entries) {
try {
File file = entry.file();
File destinationFile = new File(path + "/" + file.getName());
BufferedImage resizedImage = ResolutionManager.imageResize(file, ratio);
if (resizedImage == null) {
resizeWarnings++;
ImageIO.write(ImageIO.read(file), "png", destinationFile);
} else {
ImageIO.write(ResolutionManager.imageResize(file, ratio), "png", destinationFile);
}
} catch (IOException e) {
e.printStackTrace();
}
changePercentBy(perResizePercent);
}
return resizeWarnings;
}
private void copyTexturesFromTo(String fromPath, String toPath) {
FileHandle sourceDir = new FileHandle(fromPath);
FileHandle[] entries = sourceDir.list(Overlap2DUtils.PNG_FILTER);
float perCopyPercent = 10.0f / entries.length;
for (FileHandle entry : entries) {
File file = entry.file();
String filename = file.getName();
File target = new File(toPath + "/" + filename);
try {
FileUtils.copyFile(file, target);
} catch (IOException e) {
e.printStackTrace();
}
}
changePercentBy(perCopyPercent);
}
private File createIfNotExist(String dirPath) {
File theDir = new File(dirPath);
boolean result = false;
// if the directory does not exist, create it
if (!theDir.exists()) {
result = theDir.mkdir();
}
if (result)
return theDir;
else return null;
}
public void resizeImagesTmpDirToResolution(String packName, File sourceFolder, ResolutionEntryVO resolution, File targetFolder) {
ProjectManager projectManager = facade.retrieveProxy(ProjectManager.NAME);
float ratio = ResolutionManager.getResolutionRatio(resolution, projectManager.getCurrentProjectInfoVO().originalResolution);
if (targetFolder.exists()) {
try {
FileUtils.cleanDirectory(targetFolder);
} catch (IOException e) {
e.printStackTrace();
}
}
// now pack
TexturePacker.Settings settings = new TexturePacker.Settings();
settings.flattenPaths = true;
settings.maxHeight = getMinSquareNum(resolution.height);
settings.maxWidth = getMinSquareNum(resolution.height);
settings.filterMag = Texture.TextureFilter.Linear;
settings.filterMin = Texture.TextureFilter.Linear;
TexturePacker tp = new TexturePacker(settings);
for (final File fileEntry : sourceFolder.listFiles()) {
if (!fileEntry.isDirectory()) {
BufferedImage bufferedImage = ResolutionManager.imageResize(fileEntry, ratio);
tp.addImage(bufferedImage, FilenameUtils.removeExtension(fileEntry.getName()));
}
}
tp.pack(targetFolder, packName);
}
public float getCurrentMul() {
ProjectManager projectManager = facade.retrieveProxy(ProjectManager.NAME);
ResolutionEntryVO curRes = projectManager.getCurrentProjectInfoVO().getResolution(currentResolutionName);
float mul = 1f;
if (!currentResolutionName.equals("orig")) {
if (curRes.base == 0) {
mul = (float) curRes.width / (float) projectManager.getCurrentProjectInfoVO().originalResolution.width;
} else {
mul = (float) curRes.height / (float) projectManager.getCurrentProjectInfoVO().originalResolution.height;
}
}
return mul;
}
public void rePackProjectImagesForAllResolutions() {
ProjectManager projectManager = facade.retrieveProxy(ProjectManager.NAME);
rePackProjectImages(projectManager.getCurrentProjectInfoVO().originalResolution);
for (ResolutionEntryVO resolutionEntryVO : projectManager.getCurrentProjectInfoVO().resolutions) {
rePackProjectImages(resolutionEntryVO);
}
}
public void deleteResolution(ResolutionEntryVO resolutionEntryVO) {
ProjectManager projectManager = facade.retrieveProxy(ProjectManager.NAME);
try {
FileUtils.deleteDirectory(new File(projectManager.getCurrentProjectPath() + "/assets/" + resolutionEntryVO.name));
} catch (IOException ignored) {
ignored.printStackTrace();
}
currentResolutionName = getOriginalResolution().name;
ProjectInfoVO projectInfo = projectManager.getCurrentProjectInfoVO();
projectInfo.resolutions.removeValue(resolutionEntryVO, false);
Overlap2DFacade.getInstance().sendNotification(RESOLUTION_LIST_CHANGED);
projectManager.saveCurrentProject();
projectManager.openProjectAndLoadAllData(projectManager.getCurrentProjectPath(), "orig");
}
public Array<ResolutionEntryVO> getResolutions() {
ProjectManager projectManager = facade.retrieveProxy(ProjectManager.NAME);
return projectManager.getCurrentProjectInfoVO().resolutions;
}
public ResolutionEntryVO getOriginalResolution() {
ProjectManager projectManager = facade.retrieveProxy(ProjectManager.NAME);
return projectManager.getCurrentProjectInfoVO().originalResolution;
}
}