package com.ftloverdrive.packer;
import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.imageio.ImageIO;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import com.badlogic.gdx.tools.imagepacker.TexturePacker2;
import net.vhati.ftldat.FTLDat;
import com.ftloverdrive.packer.FTLUtilities;
public class OverdrivePacker {
private static final String ENV_APP_PATH = "OVERDRIVE_APP_PATH";
public static void main( String args[] ) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
guiInit();
}
});
}
private static void guiInit() {
List<Pattern> skipPtns = new ArrayList<Pattern>();
// Irrelevant dirs.
skipPtns.add( Pattern.compile( "^(?!img/.*[.]png$).*" ) );
// Needlessly big. It's an empty window with a tab (TODO: Crop the tab).
skipPtns.add( Pattern.compile( "img/box_options_configure[.]png" ) );
// Redundant. Pieces exist to reconstruct this.
skipPtns.add( Pattern.compile( "img/scoreUI/score_main[.]png" ) );
skipPtns.add( Pattern.compile( "img/screenshot[.]png" ) ); // Junk.
// Stripping whitespace breaks TextureRegion.split().
// http://code.google.com/p/libgdx/issues/detail?id=1192
// Not that stripping whitespace helps much for FTL anyway.
try {
UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
}
catch ( Exception e ) {
e.printStackTrace();
}
File appDir = new File( "." );
System.out.println( "CWD: "+ appDir.getAbsolutePath() );
String envAppPath = System.getenv( ENV_APP_PATH );
if ( envAppPath != null && envAppPath.length() > 0 ) {
File envAppDir = new File( envAppPath );
if ( envAppDir.exists() ) {
System.out.println( String.format( "Environment var (%s) changed app path: %s", ENV_APP_PATH, envAppPath ) );
appDir = envAppDir;
} else {
System.out.println( String.format( "Environment var (%s) set a non-existent app path: %s", ENV_APP_PATH, envAppPath ) );
}
}
File datsDir = null;
datsDir = FTLUtilities.findDatsDir();
if ( datsDir != null ) {
int response = JOptionPane.showConfirmDialog( null, "FTL resources were found in:\n"+ datsDir.getPath() +"\nIs this correct?", "Confirm", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE );
if ( response == JOptionPane.NO_OPTION ) datsDir = null;
}
if ( datsDir == null ) {
datsDir = FTLUtilities.promptForDatsDir( null );
}
if ( datsDir == null ) {
showErrorDialog( "FTL resources were not found.\nThe packer will now exit." );
System.exit( 1 );
}
JOptionPane.showMessageDialog( null, "Choose a dir (probably this one)\nin which to create Overdrive's 'resources' folder.\nAny existing 'resources' folder will be deleted.", "Destination", JOptionPane.INFORMATION_MESSAGE );
JFileChooser packChooser = new JFileChooser();
packChooser.setDialogTitle( "Choose a dir to contain resources folder" );
packChooser.setCurrentDirectory( appDir );
packChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
packChooser.setMultiSelectionEnabled(false);
if ( packChooser.showSaveDialog( null ) != JFileChooser.APPROVE_OPTION )
System.exit( 0 );
File chosenDir = packChooser.getSelectedFile();
File outputDir = new File( chosenDir, "resources" );
if ( outputDir.exists() ) deleteRecursively( outputDir );
outputDir.mkdirs();
File resDatFile = new File( datsDir, "resource.dat" );
FTLDat.FTLPack resP = null;
InputStream is = null;
OutputStream os = null;
try {
resP = new FTLDat.FTLPack( resDatFile, "r" );
TexturePacker2.Settings normalSettings = new TexturePacker2.Settings();
TexturePacker2.Settings bgSettings = new TexturePacker2.Settings();
bgSettings.paddingX = 0;
bgSettings.paddingY = 0;
List<String> allInnerPaths = new ArrayList<String>( resP.list() );
Collections.sort( allInnerPaths );
// Sort innerPaths into separate dir lists.
Map<String,List<String>> dirInnerPathsMap = new LinkedHashMap<String,List<String>>();
for ( String innerPath : allInnerPaths ) {
String dirPath = innerPath.replaceAll( "/[^/]*$", "/" );
if ( !dirInnerPathsMap.containsKey( dirPath ) ) {
dirInnerPathsMap.put( dirPath, new LinkedList<String>() );
}
dirInnerPathsMap.get( dirPath ).add( innerPath );
}
// Copy fonts.
for ( String innerPath : dirInnerPathsMap.get( "fonts/" ) ) {
File dstFile = new File( outputDir, innerPath );
dstFile.getParentFile().mkdirs();
is = resP.getInputStream( innerPath );
os = new BufferedOutputStream( new FileOutputStream( dstFile ) );
byte[] buf = new byte[4096];
int len;
while ( (len = is.read(buf)) >= 0 ) {
os.write( buf, 0, len );
}
is.close();
os.close();
}
// Pack images.
for ( Map.Entry<String,List<String>> entry : dirInnerPathsMap.entrySet() ) {
String dirPath = entry.getKey();
List<String> dirPaths = entry.getValue();
if ( dirPaths.isEmpty() || isSkippedDir( dirPaths, skipPtns ) ) continue;
File nestedOutputDir = new File( outputDir, dirPath );
TexturePacker2.Settings dirSettings = new TexturePacker2.Settings( normalSettings );
if ( dirPath.equals( "img/map/" ) ) {
dirSettings.paddingX = 0; // Some images are exactly 1024, padding puts them over the limit.
dirSettings.paddingY = 0;
}
else if ( dirPath.equals( "img/stars/" ) ) {
dirSettings.paddingX = 0; // Some images are exactly 512.
dirSettings.paddingY = 0;
}
TexturePacker2 dirPacker = new TexturePacker2( dirSettings );
for ( String innerPath : dirPaths ) {
if ( isSkippedInnerPath( innerPath, skipPtns ) ) continue;
if ( !nestedOutputDir.exists() ) nestedOutputDir.mkdirs();
InputStream imgStream = null;
try {
String baseName = innerPath.replaceAll( ".*/", "" );
baseName = baseName.replaceAll( "[.]9([.][^.]+)$", "-9$1" ); // .9.ext is special in GDX.
baseName = baseName.replaceAll( "_", "-" ); // Underscores are special in GDX.
baseName = baseName.replaceAll( "[.][^.]+$", "" ); // Strip extension.
imgStream = resP.getInputStream( innerPath );
BufferedImage rawImage = ImageIO.read( imgStream );
int rawW = rawImage.getWidth();
int rawH = rawImage.getHeight();
if ( rawW > 1024 || rawH > 1024 ) {
// Pack large images in their own atlas of fragments.
TexturePacker2 bgPacker = new TexturePacker2( bgSettings );
int regionCount = 0;
String regionName;
BufferedImage regionImage;
int regionW; int regionH;
int regionSize = 256;
for ( int regionY=0; regionY < rawH; regionY += regionSize ) {
for ( int regionX=0; regionX < rawW; regionX += regionSize ) {
regionName = baseName +"_"+ ++regionCount; // GDX treats "_123" as a frame index.
regionW = Math.min( regionSize, rawW - regionX );
regionH = Math.min( regionSize, rawH - regionY );
regionImage = rawImage.getSubimage( regionX, regionY, regionW, regionH );
bgPacker.addImage( regionImage, regionName );
}
}
bgPacker.pack( nestedOutputDir, baseName +"-bigcols"+ ((rawW+regionSize-1)/regionSize) +".atlas" );
bgPacker = null;
System.gc();
}
else {
// Add small images to the directory's shared packer.
dirPacker.addImage( rawImage, baseName );
}
}
finally {
try {if ( imgStream != null ) imgStream.close();}
catch ( IOException e ) {}
}
}
dirPacker.pack( nestedOutputDir, "pack.atlas" );
dirPacker = null;
System.gc();
}
}
catch ( IOException e ) {
e.printStackTrace();
}
finally {
try {if ( is != null ) is.close();}
catch ( IOException e ) {}
try {if ( os != null ) os.close();}
catch ( IOException e ) {}
try {if ( resP != null ) resP.close();}
catch ( IOException e ) {}
}
System.out.println( "\nPacking finished!" );
}
public static boolean isSkippedDir( List<String> dirPaths, List<Pattern> skipPtns ) {
boolean allSkipped = true;
for ( String innerPath : dirPaths ) {
if ( !isSkippedInnerPath( innerPath, skipPtns ) ) {
allSkipped = false;
break;
}
}
return allSkipped;
}
public static boolean isSkippedInnerPath( String innerPath, List<Pattern> skipPtns ) {
for ( Pattern p : skipPtns ) {
if ( p.matcher( innerPath ).matches() ) {
return true;
}
}
return false;
}
public static boolean deleteRecursively( File f ) {
if( f.exists() && f.isDirectory() ) {
File[] files = f.listFiles();
if ( files != null ) {
for ( int i=0; i < files.length; i++ ) {
deleteRecursively( files[i] );
}
}
}
return( f.delete() );
}
private static void showErrorDialog( String message ) {
JOptionPane.showMessageDialog( null, message, "Error", JOptionPane.ERROR_MESSAGE );
}
}