diff --git a/src/org/infinity/gui/converter/BamFilterBaseOutput.java b/src/org/infinity/gui/converter/BamFilterBaseOutput.java index 74d96c46d..5247f99ff 100644 --- a/src/org/infinity/gui/converter/BamFilterBaseOutput.java +++ b/src/org/infinity/gui/converter/BamFilterBaseOutput.java @@ -84,8 +84,8 @@ public static boolean convertBam(ConvertToBam converter, Path outFileName, Pseud DxtEncoder.DxtType dxtType = converter.getDxtType(); int pvrzIndex = converter.getPvrzIndex(); try { - return decoder.exportBamV2(outFileName, dxtType, pvrzIndex, converter.getProgressMonitor(), - converter.getProgressMonitorStage()); + return decoder.exportBamV2(outFileName, dxtType, pvrzIndex, BamOptionsDialog.getOverwritePvrzIndices(), + converter.getProgressMonitor(), converter.getProgressMonitorStage()); } catch (Exception e) { Logger.error(e); throw e; diff --git a/src/org/infinity/gui/converter/BamOptionsDialog.java b/src/org/infinity/gui/converter/BamOptionsDialog.java index ed5eccfc2..6cc0c7d1c 100644 --- a/src/org/infinity/gui/converter/BamOptionsDialog.java +++ b/src/org/infinity/gui/converter/BamOptionsDialog.java @@ -63,34 +63,37 @@ class BamOptionsDialog extends JDialog implements ActionListener, FocusListener, private static final String PREFS_COMPRESSBAM = "BCCompressBam"; private static final String PREFS_COMPRESSTYPE = "BCCompressionType"; private static final String PREFS_PVRZINDEX = "BCPvrzIndex"; + private static final String PREFS_OVERWRITE_PVRZ_INDICES = "BCOverwritePvrzIndices"; private static final String PREFS_RECENT_SESSIONS = "RecentSessions"; // Default settings - private static final int DEFAULT_BAM_VERSION = ConvertToBam.VERSION_BAMV1; - private static final String DEFAULT_PATH = ""; - private static final boolean DEFAULT_AUTO_CLEAR = true; - private static final boolean DEFAULT_CLOSE_ON_EXIT = false; - private static final int DEFAULT_TRANSPARENCY_THRESHOLD = 5; // in percent - private static final int DEFAULT_USE_ALPHA = ConvertToBam.ALPHA_AUTO; - private static final String DEFAULT_SORT_PALETTE = ColorConvert.SortType.None.toString(); - private static final boolean DEFAULT_COMPRESS_BAM = false; - private static final int DEFAULT_COMPRESSION_TYPE = ConvertToBam.COMPRESSION_AUTO; - private static final int DEFAULT_PVRZ_INDEX = 1000; - private static final int DEFAULT_RECENT_SESSIONS_MAX = 10; + private static final int DEFAULT_BAM_VERSION = ConvertToBam.VERSION_BAMV1; + private static final String DEFAULT_PATH = ""; + private static final boolean DEFAULT_AUTO_CLEAR = true; + private static final boolean DEFAULT_CLOSE_ON_EXIT = false; + private static final int DEFAULT_TRANSPARENCY_THRESHOLD = 5; // in percent + private static final int DEFAULT_USE_ALPHA = ConvertToBam.ALPHA_AUTO; + private static final String DEFAULT_SORT_PALETTE = ColorConvert.SortType.None.toString(); + private static final boolean DEFAULT_COMPRESS_BAM = false; + private static final int DEFAULT_COMPRESSION_TYPE = ConvertToBam.COMPRESSION_AUTO; + private static final int DEFAULT_PVRZ_INDEX = 1000; + private static final boolean DEFAULT_OVERWRITE_PVRZ_INDICES = false; + private static final int DEFAULT_RECENT_SESSIONS_MAX = 10; // Current settings private static final List recentSessions = new ArrayList<>(); - private static boolean settingsLoaded = false; - private static int bamVersion = DEFAULT_BAM_VERSION; - private static String path = DEFAULT_PATH; - private static boolean autoClear = DEFAULT_AUTO_CLEAR; - private static boolean closeOnExit = DEFAULT_CLOSE_ON_EXIT; - private static int transparencyThreshold = DEFAULT_TRANSPARENCY_THRESHOLD; - private static int useAlpha = DEFAULT_USE_ALPHA; - private static String sortPalette = DEFAULT_SORT_PALETTE; - private static boolean compressBam = DEFAULT_COMPRESS_BAM; - private static int compressionType = DEFAULT_COMPRESSION_TYPE; - private static int pvrzIndex = DEFAULT_PVRZ_INDEX; + private static boolean settingsLoaded = false; + private static int bamVersion = DEFAULT_BAM_VERSION; + private static String path = DEFAULT_PATH; + private static boolean autoClear = DEFAULT_AUTO_CLEAR; + private static boolean closeOnExit = DEFAULT_CLOSE_ON_EXIT; + private static int transparencyThreshold = DEFAULT_TRANSPARENCY_THRESHOLD; + private static int useAlpha = DEFAULT_USE_ALPHA; + private static String sortPalette = DEFAULT_SORT_PALETTE; + private static boolean compressBam = DEFAULT_COMPRESS_BAM; + private static int compressionType = DEFAULT_COMPRESSION_TYPE; + private static int pvrzIndex = DEFAULT_PVRZ_INDEX; + private static boolean overwritePvrzIndices = DEFAULT_OVERWRITE_PVRZ_INDICES; private final ConvertToBam converter; @@ -105,6 +108,7 @@ class BamOptionsDialog extends JDialog implements ActionListener, FocusListener, private JCheckBox cbCloseOnExit; private JCheckBox cbAutoClear; private JCheckBox cbCompressBam; + private JCheckBox cbOverwritePvrzIndices; private JSpinner sTransparency; private JSpinner sPvrzIndex; private JTextField tfPath; @@ -127,6 +131,7 @@ public static void loadSettings(boolean force) { compressBam = prefs.getBoolean(PREFS_COMPRESSBAM, DEFAULT_COMPRESS_BAM); compressionType = prefs.getInt(PREFS_COMPRESSTYPE, DEFAULT_COMPRESSION_TYPE); pvrzIndex = prefs.getInt(PREFS_PVRZINDEX, DEFAULT_PVRZ_INDEX); + overwritePvrzIndices = prefs.getBoolean(PREFS_OVERWRITE_PVRZ_INDICES, DEFAULT_OVERWRITE_PVRZ_INDICES); loadRecentSessions(prefs.node(PREFS_RECENT_SESSIONS)); validateSettings(); @@ -161,6 +166,7 @@ public static void saveSettings() { prefs.putBoolean(PREFS_COMPRESSBAM, compressBam); prefs.putInt(PREFS_COMPRESSTYPE, compressionType); prefs.putInt(PREFS_PVRZINDEX, pvrzIndex); + prefs.putBoolean(PREFS_OVERWRITE_PVRZ_INDICES, overwritePvrzIndices); } // Makes sure that all settings are valid. @@ -246,6 +252,11 @@ public static int getPvrzIndex() { return pvrzIndex; } + /** Returns whether to overwrite PVRZ indices of existing files in the target folder. */ + public static boolean getOverwritePvrzIndices() { + return overwritePvrzIndices; + } + /** Returns list of recently accessed session paths. */ public static List getRecentSessions() { return recentSessions; @@ -481,6 +492,7 @@ private void init() { cbCompressionType.setSelectedIndex(getCompressionType()); model = new SpinnerNumberModel(getPvrzIndex(), 0, 99999, 1); sPvrzIndex = new JSpinner(model); + cbOverwritePvrzIndices = new JCheckBox("Overwrite existing PVRZ files", getOverwritePvrzIndices()); JPanel pBamV2 = new JPanel(new GridBagLayout()); pBamV2.setBorder(BorderFactory.createTitledBorder("PVRZ-based BAM ")); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, @@ -495,7 +507,10 @@ private void init() { c = ViewerUtil.setGBC(c, 1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 4, 4, 4), 0, 0); pBamV2.add(sPvrzIndex, c); - c = ViewerUtil.setGBC(c, 0, 2, 2, 1, 1.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, + c = ViewerUtil.setGBC(c, 0, 2, 2, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(4, 4, 0, 0), 0, 0); + pBamV2.add(cbOverwritePvrzIndices, c); + c = ViewerUtil.setGBC(c, 0, 3, 2, 1, 1.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0); pBamV2.add(new JPanel(), c); // filler component @@ -565,6 +580,7 @@ private void setDefaults() { cbCompressBam.setSelected(DEFAULT_COMPRESS_BAM); cbCompressionType.setSelectedIndex(DEFAULT_COMPRESSION_TYPE); sPvrzIndex.setValue(DEFAULT_PVRZ_INDEX); + cbOverwritePvrzIndices.setSelected(DEFAULT_OVERWRITE_PVRZ_INDICES); } // Fetches the values from the dialog controls @@ -579,6 +595,7 @@ private void updateSettings() { compressBam = cbCompressBam.isSelected(); compressionType = cbCompressionType.getSelectedIndex(); pvrzIndex = (Integer) sPvrzIndex.getValue(); + overwritePvrzIndices = cbOverwritePvrzIndices.isSelected(); validateSettings(); // transparency options may have changed: force palette generation diff --git a/src/org/infinity/resource/graphics/PseudoBamDecoder.java b/src/org/infinity/resource/graphics/PseudoBamDecoder.java index b2badc75b..05066987d 100644 --- a/src/org/infinity/resource/graphics/PseudoBamDecoder.java +++ b/src/org/infinity/resource/graphics/PseudoBamDecoder.java @@ -17,6 +17,7 @@ import java.awt.image.IndexColorModel; import java.io.OutputStream; import java.nio.ByteBuffer; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; @@ -885,8 +886,8 @@ public boolean exportBamV1(Path fileName, ProgressMonitor progress, int curProgr * @return {@code true} if the export was successful, {@code false} otherwise. * @throws Exception If an unrecoverable error occured. */ - public boolean exportBamV2(Path fileName, DxtEncoder.DxtType dxtType, int pvrzIndex, ProgressMonitor progress, - int curProgress) throws Exception { + public boolean exportBamV2(Path fileName, DxtEncoder.DxtType dxtType, int pvrzIndex, boolean overwrite, + ProgressMonitor progress, int curProgress) throws Exception { final int FrameEntrySize = 12; final int CycleEntrySize = 4; final int BlockEntrySize = 28; @@ -924,6 +925,28 @@ public boolean exportBamV2(Path fileName, DxtEncoder.DxtType dxtType, int pvrzIn return false; } + if (!overwrite) { + // adjusting pvrz indices + final HashMap indexMap = new HashMap<>(Math.max(4, listGrid.size() + listGrid.size() / 2)); + for (final FrameDataV2 frame : listFrameData) { + final int newIndex = indexMap.computeIfAbsent(frame.page, index -> { + for (int i = index; i < 100_000; i++) { + if (!indexMap.containsValue(i)) { + final Path pvrzPath = pvrzFilePath.resolve(getPvrzFileName(i)); + if (!Files.exists(pvrzPath)) { + return i; + } + } + } + return -1; + }); + if (newIndex < 0) { + throw new Exception("Effective PVRZ index is out of range [0..99999]."); + } + frame.page = newIndex; + } + } + // generating remaining info blocks List listFrameDataBlocks = new ArrayList<>(); List listFrameEntries = new ArrayList<>(); @@ -1216,6 +1239,17 @@ public static void unregisterColors(HashMap colorMap, Buffered } } + /** + * Returns a PVRZ filename (without path) for the specified index. Throws an {@link IndexOutOfBoundsException} + * if the index is out of bounds. + */ + public static String getPvrzFileName(int index) throws IndexOutOfBoundsException { + if (index < 0 || index > 99999) { + throw new IndexOutOfBoundsException("Pvrz index is out of bounds: " + index); + } + return String.format("MOS%04d.PVRZ", index); + } + // Calculates the locations of all frames on PVRZ textures and stores the results in framesList and gridList. private boolean buildFrameDataList(List framesList, List gridList, int pvrzPageIndex) throws Exception { @@ -1271,50 +1305,51 @@ private boolean createPvrzPages(Path path, DxtEncoder.DxtType dxtType, List pageSet = new HashSet<>(); for (FrameDataV2 entry : framesList) { - pageMin = Math.min(pageMin, entry.page); - pageMax = Math.max(pageMax, entry.page); + pageSet.add(entry.page); } + final List pageList = new ArrayList<>(pageSet); + pageList.sort(null); String note = "Generating PVRZ file %s / %s"; if (progress != null) { if (curProgress < 0) { curProgress = 0; } - progress.setMaximum(curProgress + pageMax - pageMin + 1); + progress.setMaximum(curProgress + pageList.size()); progress.setProgress(curProgress++); } // processing each PVRZ page - for (int i = pageMin; i <= pageMax; i++) { + for (int i = 0; i < pageList.size(); i++) { if (progress != null) { if (progress.isCanceled()) { throw new Exception("Conversion has been cancelled by the user."); } progress.setProgress(curProgress); - progress.setNote(String.format(note, curProgress, pageMax - pageMin + 1)); + progress.setNote(String.format(note, curProgress, pageList.size())); curProgress++; } - Path pvrzName = path.resolve(String.format("MOS%04d.PVRZ", i)); - BinPack2D packer = gridList.get(i - pageMin); + final int pageIndex = pageList.get(i); + final Path pvrzName = path.resolve(getPvrzFileName(pageIndex)); + final BinPack2D packer = gridList.get(i); packer.shrinkBin(true); // generating texture image int tw = packer.getBinWidth(); int th = packer.getBinHeight(); - BufferedImage texture = ColorConvert.createCompatibleImage(tw, th, true); + final BufferedImage texture = ColorConvert.createCompatibleImage(tw, th, true); Graphics2D g = texture.createGraphics(); try { g.setComposite(AlphaComposite.Src); g.setColor(ColorConvert.TRANSPARENT_COLOR); g.fillRect(0, 0, texture.getWidth(), texture.getHeight()); for (int frameIdx = 0; frameIdx < listFrames.size(); frameIdx++) { - BufferedImage image = listFrames.get(frameIdx).frame; - FrameDataV2 frame = framesList.get(frameIdx); - if (frame.page == i) { + final BufferedImage image = listFrames.get(frameIdx).frame; + final FrameDataV2 frame = framesList.get(frameIdx); + if (frame.page == pageIndex) { int sx = frame.dx, sy = frame.dy; int dx = frame.sx, dy = frame.sy; int w = frame.width, h = frame.height; @@ -2181,5 +2216,11 @@ public boolean equals(Object obj) { return dx == other.dx && dy == other.dy && height == other.height && page == other.page && sx == other.sx && sy == other.sy && width == other.width; } + + @Override + public String toString() { + return "FrameDataV2 [page=" + page + ", sx=" + sx + ", sy=" + sy + ", width=" + width + ", height=" + height + + ", dx=" + dx + ", dy=" + dy + "]"; + } } }