// Near Infinity - An Infinity Engine Browser and Editor // Copyright (C) 2001 - 2005 Jon Olav Hauglid // See LICENSE.txt for license information package org.infinity.gui.converter; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.BufferedImage; import java.awt.image.DataBufferInt; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; import java.util.List; import java.util.Vector; import javax.imageio.ImageIO; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.ListSelectionModel; import javax.swing.ProgressMonitor; import javax.swing.SwingWorker; import javax.swing.filechooser.FileNameExtensionFilter; import org.infinity.gui.ChildFrame; import org.infinity.gui.ViewerUtil; import org.infinity.gui.WindowBlocker; import org.infinity.icon.Icons; import org.infinity.resource.Profile; import org.infinity.resource.graphics.ColorConvert; import org.infinity.resource.graphics.Compressor; import org.infinity.resource.graphics.DxtEncoder; import org.infinity.util.DynamicArray; import org.infinity.util.SimpleListModel; import org.infinity.util.io.FileManager; import org.infinity.util.io.StreamUtils; public class ConvertToPvrz extends ChildFrame implements ActionListener, PropertyChangeListener { private static String currentDir = Profile.getGameRoot().toString(); private JList<Path> lInputList; private SimpleListModel<Path> lInputModel; private JButton bConvert, bCancel; private JButton bInputAdd, bInputAddFolder, bInputRemove, bInputRemoveAll; private JButton bTargetDir, bCompressionHelp; private JTextField tfTargetDir; private JComboBox<String> cbOverwrite, cbCompression; private JCheckBox cbCloseOnExit; private SwingWorker<List<String>, Void> workerConvert; private ProgressMonitor progress; private WindowBlocker blocker; // Returns a list of supported graphics file formats private static FileNameExtensionFilter[] getInputFilters() { FileNameExtensionFilter[] filters = new FileNameExtensionFilter[] { new FileNameExtensionFilter("Graphics files (*.bmp, *.png, *,jpg, *.jpeg)", "bam", "bmp", "png", "jpg", "jpeg"), new FileNameExtensionFilter("BMP files (*.bmp)", "bmp"), new FileNameExtensionFilter("PNG files (*.png)", "png"), new FileNameExtensionFilter("JPEG files (*.jpg, *.jpeg)", "jpg", "jpeg") }; return filters; } /** * Creates a PVR header based on the parameters specified. * @param width Texture width in pixels. * @param height Texture height in pixels. * @param pixelFormat Internal pixel format code. * @return The PVR header as byte array. */ public static byte[] createPVRHeader(int width, int height, int pixelFormat) { byte[] header = new byte[0x34]; DynamicArray.putInt(header, 0, 0x03525650); // signature DynamicArray.putInt(header, 4, 0); // flags DynamicArray.putInt(header, 8, pixelFormat); // pixel format DynamicArray.putInt(header, 12, 0); // pixel format (extension) DynamicArray.putInt(header, 16, 0); // color space (0=linear rgb) DynamicArray.putInt(header, 20, 0); // channel type (0=unsigned byte normalized) DynamicArray.putInt(header, 24, height); // height DynamicArray.putInt(header, 28, width); // width DynamicArray.putInt(header, 32, 1); // depth (in pixels) DynamicArray.putInt(header, 36, 1); // # surfaces DynamicArray.putInt(header, 40, 1); // # faces DynamicArray.putInt(header, 44, 1); // # mipmap levels DynamicArray.putInt(header, 48, 0); // # meta data size return header; } /** * Calculates the first available power of two value that is greater than the specified value. * @param value The value that should fit into the resulting power of two value. * @return A power of two value. */ public static int nextPowerOfTwo(int value) { int count = 0, pos = 0; for (int i = 0, tmp = value; i < 32; i++) { if ((tmp & 1) != 0) { count++; pos = i; } tmp >>>= 1; } if (count != 1) { value = (count < 1) ? 4 : (1 << (pos+1)); } return value; } public ConvertToPvrz() { super("Convert to PVRZ", true); init(); } //--------------------- Begin Class ChildFrame --------------------- @Override protected boolean windowClosing(boolean forced) throws Exception { clear(); return super.windowClosing(forced); } //--------------------- End Class ChildFrame --------------------- //--------------------- Begin Interface ActionListener --------------------- @Override public void actionPerformed(ActionEvent event) { if (event.getSource() == bConvert) { workerConvert = new SwingWorker<List<String>, Void>() { @Override public List<String> doInBackground() { // returns summary of the completed conversion return convert(); } }; workerConvert.addPropertyChangeListener(this); blocker = new WindowBlocker(this); blocker.setBlocked(true); workerConvert.execute(); } else if (event.getSource() == bCancel) { hideWindow(); } else if (event.getSource() == bInputAdd) { JFileChooser fc = new JFileChooser(currentDir); fc.setDialogTitle("Choose files"); fc.setDialogType(JFileChooser.OPEN_DIALOG); fc.setFileSelectionMode(JFileChooser.FILES_ONLY); fc.setMultiSelectionEnabled(true); FileNameExtensionFilter[] filters = getInputFilters(); for (final FileNameExtensionFilter filter: filters) { fc.addChoosableFileFilter(filter); } fc.setFileFilter(filters[0]); int ret = fc.showOpenDialog(this); if (ret == JFileChooser.APPROVE_OPTION) { File[] files = fc.getSelectedFiles(); if (files != null && files.length > 0) { currentDir = files[0].getParent(); // add to list box for (final File f: files) { lInputModel.addElement(f.toPath()); } } } fc = null; bConvert.setEnabled(isReady()); } else if (event.getSource() == bInputAddFolder) { JFileChooser fc = new JFileChooser(currentDir); fc.setDialogTitle("Choose directory"); fc.setDialogType(JFileChooser.OPEN_DIALOG); fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); int ret = fc.showOpenDialog(this); if (ret == JFileChooser.APPROVE_OPTION) { // adding all files in the directory File dir = fc.getSelectedFile(); if (dir != null && dir.isDirectory()) { currentDir = dir.toString(); FileNameExtensionFilter[] filters = getInputFilters(); File[] fileList = dir.listFiles(); for (final File file: fileList) { for (final FileNameExtensionFilter filter: filters) { if (file != null && file.isFile() && filter.accept(file)) { lInputModel.addElement(file.toPath()); break; } } } } } fc = null; bConvert.setEnabled(isReady()); } else if (event.getSource() == bInputRemove) { int[] indices = lInputList.getSelectedIndices(); for (int i = indices.length - 1; i >= 0; i--) { lInputModel.remove(indices[i]); } bConvert.setEnabled(isReady()); } else if (event.getSource() == bInputRemoveAll) { lInputModel.clear(); bConvert.setEnabled(isReady()); } else if (event.getSource() == bTargetDir) { JFileChooser fc = new JFileChooser(currentDir); fc.setDialogTitle("Choose target directory"); fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); if (!tfTargetDir.getText().isEmpty()) { fc.setSelectedFile(FileManager.resolve(tfTargetDir.getText()).toFile()); } int ret = fc.showOpenDialog(this); if (ret == JFileChooser.APPROVE_OPTION) { currentDir = fc.getSelectedFile().toString(); tfTargetDir.setText(fc.getSelectedFile().toString()); } fc = null; bConvert.setEnabled(isReady()); } else if (event.getSource() == bCompressionHelp) { final String helpMsg = "\"DXT1\" provides the highest compression ratio. It supports only 1 bit alpha\n" + "(i.e. either no or full transparency) and is the preferred type for TIS or MOS resources.\n\n" + "\"DXT5\" provides an average compression ratio. It features interpolated\n" + "alpha transitions and is the preferred type for BAM resources.\n\n" + "\"Auto\" selects the most appropriate compression type based on the input data."; JOptionPane.showMessageDialog(this, helpMsg, "About Compression Types", JOptionPane.INFORMATION_MESSAGE); } } //--------------------- End Interface ActionListener --------------------- //--------------------- Begin Interface PropertyChangeListener --------------------- @Override public void propertyChange(PropertyChangeEvent event) { if (event.getSource() == workerConvert) { if ("state".equals(event.getPropertyName()) && SwingWorker.StateValue.DONE == event.getNewValue()) { if (blocker != null) { blocker.setBlocked(false); blocker = null; } if (progress != null) { progress.close(); progress = null; } List<String> sl = null; try { sl = workerConvert.get(); } catch (Exception e) { e.printStackTrace(); } workerConvert = null; boolean isError = false; String s = null; if (sl != null && !sl.isEmpty()) { if (sl.get(0) != null) { s = sl.get(0); } else if (sl.size() > 1 && sl.get(1) != null) { s = sl.get(1); isError = true; } } if (s != null) { if (isError) { JOptionPane.showMessageDialog(this, s, "Error", JOptionPane.ERROR_MESSAGE); } else { JOptionPane.showMessageDialog(this, s, "Information", JOptionPane.INFORMATION_MESSAGE); if (cbCloseOnExit.isSelected()) { hideWindow(); } else { clear(); } } } else { JOptionPane.showMessageDialog(this, "Unknown error!", "Error", JOptionPane.ERROR_MESSAGE); } } } } //--------------------- End Interface PropertyChangeListener --------------------- private void init() { setIconImage(Icons.getImage(Icons.ICON_APPLICATION_16)); // setting up input section JPanel pInputAdd = new JPanel(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); bInputAdd = new JButton("Add..."); bInputAdd.addActionListener(this); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 4, 0), 16, 0); pInputAdd.add(bInputAdd, c); bInputAddFolder = new JButton("Add folder..."); bInputAddFolder.addActionListener(this); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 16, 0); pInputAdd.add(bInputAddFolder, c); JPanel pInputRemove = new JPanel(new GridBagLayout()); bInputRemove = new JButton("Remove"); bInputRemove.addActionListener(this); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 4, 0), 16, 0); pInputRemove.add(bInputRemove, c); bInputRemoveAll = new JButton("Remove all"); bInputRemoveAll.addActionListener(this); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 16, 0); pInputRemove.add(bInputRemoveAll, c); JPanel pInputCtrl = new JPanel(new BorderLayout()); pInputCtrl.add(pInputAdd, BorderLayout.WEST); pInputCtrl.add(pInputRemove, BorderLayout.EAST); lInputModel = new SimpleListModel<>(); lInputList = new JList<>(lInputModel); lInputList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); JScrollPane scroll = new JScrollPane(lInputList); JPanel pInput = new JPanel(new GridBagLayout()); pInput.setBorder(BorderFactory.createTitledBorder("Input ")); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(0, 4, 0, 4), 0, 0); pInput.add(scroll, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 4, 4, 4), 0, 0); pInput.add(pInputCtrl, c); // setting up output section JLabel lTargetDir = new JLabel("Directory:"); JLabel lOverwrite = new JLabel("Overwrite:"); JLabel lCompression = new JLabel("Compression type:"); tfTargetDir = new JTextField(); bTargetDir = new JButton("..."); bTargetDir.addActionListener(this); cbOverwrite = new JComboBox<>(new String[]{"Ask", "Replace", "Skip"}); cbOverwrite.setSelectedIndex(1); cbCompression = new JComboBox<>(new String[]{"Auto", "DXT1", "DXT5"}); cbCompression.setSelectedIndex(0); bCompressionHelp = new JButton("?"); bCompressionHelp.setToolTipText("About compression types"); bCompressionHelp.addActionListener(this); bCompressionHelp.setMargin(new Insets(bCompressionHelp.getMargin().top, 4, bCompressionHelp.getMargin().bottom, 4)); JPanel pOutputSub = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 8, 0); pOutputSub.add(cbOverwrite, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 16, 0, 0), 0, 0); pOutputSub.add(lCompression, c); c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 8, 0); pOutputSub.add(cbCompression, c); c = ViewerUtil.setGBC(c, 3, 0, 2, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); pOutputSub.add(bCompressionHelp, c); JPanel pOutput = new JPanel(new GridBagLayout()); pOutput.setBorder(BorderFactory.createTitledBorder("Output ")); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); pOutput.add(lTargetDir, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 8, 0, 0), 0, 0); pOutput.add(tfTargetDir, c); c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 4, 0, 4), 0, 0); pOutput.add(bTargetDir, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); pOutput.add(lOverwrite, c); c = ViewerUtil.setGBC(c, 1, 1, GridBagConstraints.REMAINDER, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(4, 8, 4, 0), 0, 0); pOutput.add(pOutputSub, c); // setting up bottom button bar cbCloseOnExit = new JCheckBox("Close dialog after conversion", true); bConvert = new JButton("Start Conversion"); bConvert.addActionListener(this); bConvert.setEnabled(isReady()); Insets i = bConvert.getInsets(); bConvert.setMargin(new Insets(i.top + 2, i.left, i.bottom + 2, i.right)); bCancel = new JButton("Cancel"); bCancel.addActionListener(this); i = bCancel.getInsets(); bCancel.setMargin(new Insets(i.top + 2, i.left, i.bottom + 2, i.right)); JPanel pButtons = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); pButtons.add(cbCloseOnExit, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pButtons.add(new JPanel(), c); c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); pButtons.add(bConvert, c); c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); pButtons.add(bCancel, c); // putting all together setLayout(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(8, 8, 0, 8), 0, 0); add(pInput, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 8), 0, 0); add(pOutput, c); c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 8, 8, 8), 0, 0); add(pButtons, c); // finalizing dialog initialization pack(); setMinimumSize(getPreferredSize()); setLocationRelativeTo(getParent()); setVisible(true); } private void hideWindow() { clear(); setVisible(false); } // resetting dialog state private void clear() { lInputModel.clear(); bConvert.setEnabled(isReady()); } // got enough data to start conversion? private boolean isReady() { return !lInputModel.isEmpty(); } // checks graphics input file properties private static boolean isValidInput(Path inFile) { boolean result = (inFile != null && Files.isRegularFile(inFile)); if (result) { Dimension d = ColorConvert.getImageDimension(inFile); if (d == null || d.width <= 0 || d.width > 1024 || d.height <= 0 || d.height > 1024) { result = false; } } return result; } // Convert source image(s) into the PVRZ format. Returns a short summary of the conversion process. // Return value: First list element is used for success message, second element for error message. private List<String> convert() { // fetching required information boolean ask = false, skip = false; switch (cbOverwrite.getSelectedIndex()) { case 0: ask = true; break; case 2: skip = true; break; } boolean auto = false; int dxt = 1; switch (cbCompression.getSelectedIndex()) { case 0: auto = true; break; case 2: dxt = 5; break; } Path targetPath = FileManager.resolve(""); if (tfTargetDir.getText() != null && !tfTargetDir.getText().isEmpty()) { targetPath = FileManager.resolve(tfTargetDir.getText()); } if (!Files.isDirectory(targetPath)) { List<String> l = new Vector<String>(2); l.add(null); l.add("Invalid target directory specified. No conversion takes place."); return l; } if (lInputModel.isEmpty()) { List<String> l = new Vector<String>(2); l.add(null); l.add("No source file(s) specified. No conversion takes place."); return l; } Path[] inputFiles = new Path[lInputModel.size()]; for (int i = 0; i < lInputModel.size(); i++) { inputFiles[i] = lInputModel.get(i); } boolean isSingle = inputFiles.length == 1; // preparing progress meter final String note = "Converting file %1$d / %2$d"; int progressIndex = 0, progressInc = 1; int progressMax = isSingle ? 100 : inputFiles.length; progress = new ProgressMonitor(this, "Converting PVRZ...", isSingle ? null : String.format(note, 0, progressMax), 0, progressMax+1); progress.setMillisToDecideToPopup(500); progress.setMillisToPopup(2000); // starting conversion int skippedFiles = 0, warnings = 0, errors = 0; for (int fileIdx = 0; fileIdx < inputFiles.length; fileIdx++) { progress.setProgress(progressIndex); if (!isSingle) { progress.setNote(String.format(note, progressIndex+1, progressMax)); progressIndex += progressInc; } Path inFile = inputFiles[fileIdx]; if (isValidInput(inFile)) { String inFileName = inFile.getFileName().toString(); // generating output filename String outFileName = null; int n = inFileName.lastIndexOf('.'); if (n > 0) { outFileName = inFileName.substring(0, n) + ".PVRZ"; } else { outFileName = inFileName + ".PVRZ"; } Path outFile = targetPath.resolve(outFileName); // handling overwrite existing file if (Files.exists(outFile)) { if (skip) { skippedFiles++; continue; } else if (ask) { String msg = String.format("File \"%1$s\" aready exists. Overwrite?", outFileName); int ret = JOptionPane.showConfirmDialog(this, msg, "Overwrite?", JOptionPane.YES_NO_OPTION); if (ret == JOptionPane.NO_OPTION) { skippedFiles++; continue; } } } // loading source image data BufferedImage srcImg = null; try { srcImg = ColorConvert.toBufferedImage(ImageIO.read(inFile.toFile()), true); } catch (Exception e) { } if (srcImg == null) { skippedFiles++; continue; } // handling "auto" compression format int[] pixels = ((DataBufferInt)srcImg.getRaster().getDataBuffer()).getData(); if (auto) { for (n = 0; n < pixels.length; n++) { int alpha = pixels[n] >>> 24; if (alpha > 0x20 && alpha < 0xe0) { dxt = 5; break; } } } // ensure dimensions are always power of two int w = nextPowerOfTwo(srcImg.getWidth()); int h = nextPowerOfTwo(srcImg.getHeight()); if (w != srcImg.getWidth() || h != srcImg.getHeight()) { BufferedImage image = ColorConvert.createCompatibleImage(w, h, true); Graphics2D g = image.createGraphics(); g.drawImage(srcImg, 0, 0, null); g.dispose(); srcImg = image; pixels = ((DataBufferInt)srcImg.getRaster().getDataBuffer()).getData(); } // preparing output DxtEncoder.DxtType dxtType = null; byte[] header = null; switch (dxt) { case 3: dxtType = DxtEncoder.DxtType.DXT3; header = createPVRHeader(w, h, 9); break; case 5: dxtType = DxtEncoder.DxtType.DXT5; header = createPVRHeader(w, h, 11); break; default: dxtType = DxtEncoder.DxtType.DXT1; header = createPVRHeader(w, h, 7); } // encoding block by block int outSize = DxtEncoder.calcImageSize(w, h, dxtType); byte[] output = new byte[outSize]; int outOfs = 0; int bw = w / 4; int bh = h / 4; int[] inBlock = new int[16]; byte[] outBlock = new byte[DxtEncoder.calcBlockSize(dxtType)]; // more initialization for progress meter int counter = 0; if (isSingle) { progressInc = (bw*bh / 100); if (progressInc == 0) { progress.setMaximum(bw*bh + 1); progressInc = 1; } } for (int y = 0; y < bh; y++) { if (!isSingle) { // force the progress meter to pop up progress.setProgress(progressIndex); } for (int x = 0; x < bw; x++) { // handling progress meter if (isSingle) { if (counter >= progressInc) { counter = 0; progressIndex++; progress.setProgress(progressIndex); } counter++; } if (progress.isCanceled()) { progress.close(); progress = null; List<String> l = new Vector<String>(2); l.add(null); l.add("Conversion cancelled."); return l; } // starting encoding process int ofs = y*w*4 + x*4; for (int i = 0; i < 4; i++, ofs+=w) { System.arraycopy(pixels, ofs, inBlock, i*4, 4); } try { DxtEncoder.encodeBlock(inBlock, outBlock, dxtType); } catch (Exception e) { warnings++; Arrays.fill(outBlock, (byte)0); } System.arraycopy(outBlock, 0, output, outOfs, outBlock.length); outOfs += outBlock.length; } } // finalizing output data byte[] pvrz = new byte[header.length + output.length]; System.arraycopy(header, 0, pvrz, 0, header.length); System.arraycopy(output, 0, pvrz, header.length, output.length); pvrz = Compressor.compress(pvrz, 0, pvrz.length, true); try (OutputStream os = StreamUtils.getOutputStream(outFile, true)) { os.write(pvrz); } catch (Exception e) { errors++; e.printStackTrace(); } // cleaning up pixels = null; srcImg.flush(); srcImg = null; output = null; pvrz = null; inBlock = null; outBlock = null; header = null; } else { warnings++; skippedFiles++; } } progress.close(); progress = null; // constructing failure/success message List<String> l = new Vector<String>(2); StringBuilder sb = new StringBuilder(); if (warnings == 0 && errors == 0) { sb.append("Conversion finished successfully."); } else { l.add(null); if (warnings > 0 && errors == 0) { sb.append(String.format("Conversion finished with %1$d warning(s).", warnings)); } else { sb.append(String.format("Conversion finished with %1$d warning(s) and %2$d error(s).", warnings, errors)); } } if (skippedFiles > 0) { if (skippedFiles == 1) { sb.append(String.format("\n%1$d file has been skipped.", skippedFiles)); } else { sb.append(String.format("\n%1$d files have been skipped.", skippedFiles)); } } l.add(sb.toString()); return l; } }