// 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.Component; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Image; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.image.BufferedImage; import java.awt.image.DataBufferInt; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.IOException; import java.io.OutputStream; import java.io.File; import java.nio.ByteBuffer; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import javax.imageio.ImageIO; import javax.imageio.ImageReader; import javax.imageio.stream.ImageInputStream; 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.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.util.SimpleListModel; import org.infinity.util.io.FileManager; import org.infinity.util.io.StreamUtils; public class ConvertToBmp extends ChildFrame implements ActionListener, FocusListener, PropertyChangeListener { private static Path currentPath = Profile.getGameRoot(); private SimpleListModel<String> modelInputFiles; private JList<String> listInputFiles; private JButton bAdd, bAddFolder, bRemove, bRemoveAll; private JTextField tfOutput; private JButton bOutput; private JButton bConvert, bCancel; private JCheckBox cbCloseOnExit, cbEnableAlpha, cbFixPremultipliedAlpha; private JComboBox<String> cbOverwrite; private SwingWorker<List<String>, Void> workerConvert; private WindowBlocker blocker; // Returns a list of supported graphics file formats private static FileNameExtensionFilter[] getGraphicsFilters() { FileNameExtensionFilter[] filters = new FileNameExtensionFilter[] { new FileNameExtensionFilter("Graphics files (*.bmp, *.png, *,jpg, *.jpeg)", "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; } // returns a selection of files private static Path[] getOpenFileName(Component parent, String title, Path rootPath, boolean selectMultiple, FileNameExtensionFilter[] filters, int filterIndex) { if (rootPath == null) { rootPath = currentPath; } Path file = FileManager.resolve(rootPath); JFileChooser fc = new JFileChooser(file.toFile()); if (!Files.isDirectory(file)) { fc.setSelectedFile(file.toFile()); } if (title == null) { title = selectMultiple ? "Select file(s)" : "Select file"; } fc.setDialogTitle(title); fc.setDialogType(JFileChooser.OPEN_DIALOG); fc.setFileSelectionMode(JFileChooser.FILES_ONLY); fc.setMultiSelectionEnabled(selectMultiple); if (filters != null) { for (final FileNameExtensionFilter filter: filters) { fc.addChoosableFileFilter(filter); } if (filterIndex >= 0 && filterIndex < filters.length) { fc.setFileFilter(filters[filterIndex]); } } if (fc.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) { if (selectMultiple) { if (fc.getSelectedFiles().length > 0) { currentPath = fc.getSelectedFiles()[0].toPath().getParent(); } File[] files = fc.getSelectedFiles(); Path[] paths = new Path[files.length]; for (int i = 0; i < files.length; i++) { paths[i] = files[i].toPath(); } return paths; } else { file = fc.getSelectedFile().toPath(); currentPath = file.getParent(); return new Path[]{file.getFileName()}; } } else { return null; } } // returns a path name private static Path getOpenPathName(Component parent, String title, Path rootPath) { if (rootPath == null) { rootPath = currentPath; } JFileChooser fc = new JFileChooser(rootPath.toFile()); if (title == null) { title = "Select folder"; } fc.setDialogTitle(title); fc.setDialogType(JFileChooser.OPEN_DIALOG); fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); if (fc.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) { currentPath = fc.getSelectedFile().toPath(); return fc.getSelectedFile().toPath(); } else { return null; } } public ConvertToBmp() { super("Convert to BMP", 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() { return convert(); } }; blocker = new WindowBlocker(this); blocker.setBlocked(true); workerConvert.addPropertyChangeListener(this); workerConvert.execute(); } else if (event.getSource() == bCancel) { setVisible(false); } else if (event.getSource() == bAdd) { inputAdd(); } else if (event.getSource() == bAddFolder) { inputAddFolder(); } else if (event.getSource() == bRemove) { inputRemove(); } else if (event.getSource() == bRemoveAll) { inputRemoveAll(); } else if (event.getSource() == bOutput) { setOutput(); } else if (event.getSource() == cbEnableAlpha) { cbFixPremultipliedAlpha.setEnabled(cbEnableAlpha.isSelected()); } } //--------------------- 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; } 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 --------------------- //--------------------- Begin Interface FocusListener --------------------- @Override public void focusGained(FocusEvent event) { // nothing to do } @Override public void focusLost(FocusEvent event) { if (event.getSource() == tfOutput) { // bConvert.setEnabled(isReady()); } } //--------------------- End Interface FocusListener --------------------- private void init() { setIconImage(Icons.getImage(Icons.ICON_APPLICATION_16)); GridBagConstraints c = new GridBagConstraints(); bAdd = new JButton("Add..."); bAdd.addActionListener(this); bAddFolder = new JButton("Add folder..."); bAddFolder.addActionListener(this); JPanel pAdd = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pAdd.add(bAdd, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0); pAdd.add(bAddFolder, c); bRemove = new JButton("Remove"); bRemove.addActionListener(this); bRemoveAll = new JButton("Remove all"); bRemoveAll.addActionListener(this); JPanel pRemove = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pRemove.add(bRemove, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0); pRemove.add(bRemoveAll, c); JPanel pInputButtons = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pInputButtons.add(pAdd, 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); pInputButtons.add(new JPanel(), c); c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pInputButtons.add(pRemove, c); modelInputFiles = new SimpleListModel<String>(); listInputFiles = new JList<>(modelInputFiles); JScrollPane scroll = new JScrollPane(listInputFiles); JPanel pInputFrame = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0); pInputFrame.add(scroll, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0); pInputFrame.add(pInputButtons, c); 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(4, 4, 4, 4), 0, 0); pInput.add(pInputFrame, c); JLabel lOutput = new JLabel("Directory:"); tfOutput = new JTextField(); tfOutput.addFocusListener(this); bOutput = new JButton("..."); bOutput.addActionListener(this); JLabel lOverwrite = new JLabel("Overwrite:"); cbOverwrite = new JComboBox<>(new String[]{"Ask", "Replace", "Skip"}); cbOverwrite.setSelectedIndex(1); cbEnableAlpha = new JCheckBox("Enable transparency support", true); cbEnableAlpha.setToolTipText("Activate to create bitmap files with alpha channel"); cbEnableAlpha.addActionListener(this); cbFixPremultipliedAlpha = new JCheckBox("Fix premultiplied alpha", false); cbFixPremultipliedAlpha.setEnabled(cbEnableAlpha.isSelected()); cbFixPremultipliedAlpha.setToolTipText("Activate if the resulting BMP image " + "differs from the source image"); JPanel pOutputDir = 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); pOutputDir.add(lOutput, 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); pOutputDir.add(tfOutput, c); c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); pOutputDir.add(bOutput, c); JPanel pOutputOptions = 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); pOutputOptions.add(lOverwrite, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 8, 0, 0), 4, 0); pOutputOptions.add(cbOverwrite, c); c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 16, 0, 0), 0, 0); pOutputOptions.add(cbEnableAlpha, c); c = ViewerUtil.setGBC(c, 0, 1, 2, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); pOutputOptions.add(new JPanel(), c); c = ViewerUtil.setGBC(c, 2, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(4, 16, 0, 0), 0, 0); pOutputOptions.add(cbFixPremultipliedAlpha, c); JPanel pOutputFrame = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pOutputFrame.add(pOutputDir, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0); pOutputFrame.add(pOutputOptions, c); JPanel pOutput = new JPanel(new GridBagLayout()); pOutput.setBorder(BorderFactory.createTitledBorder("Output ")); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 4, 4, 4), 0, 0); pOutput.add(pOutputFrame, c); cbCloseOnExit = new JCheckBox("Close dialog after conversion", true); bConvert = new JButton("Start Conversion"); bConvert.addActionListener(this); 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, 1.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, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); pButtons.add(bConvert, c); c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); pButtons.add(bCancel, c); setLayout(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(8, 8, 0, 8), 0, 0); add(pInput, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 8, 0, 8), 0, 0); add(pOutput, c); c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 8, 8, 8), 0, 0); add(pButtons, c); setPreferredSize(new Dimension(getPreferredSize().width+50, getPreferredSize().height + 50)); setMinimumSize(getPreferredSize()); pack(); setLocationRelativeTo(getParent()); updateStatus(); setVisible(true); } private void clear() { inputRemoveAll(); updateStatus(); } private void hideWindow() { clear(); setVisible(false); } private void updateStatus() { boolean enabled = (!modelInputFiles.isEmpty() && !tfOutput.getText().isEmpty()); bConvert.setEnabled(enabled); } // checks for valid graphics file private boolean isValidInput(Path file) { boolean result = false; if (file != null) { try (ImageInputStream iis = ImageIO.createImageInputStream(file.toFile())) { final Iterator<ImageReader> readers = ImageIO.getImageReaders(iis); if (readers.hasNext()) { result = true; } iis.close(); } catch (Exception e) { } } return result; } private void inputAdd() { Path rootPath = null; if (!modelInputFiles.isEmpty()) { rootPath = FileManager.resolve(modelInputFiles.get(modelInputFiles.size() - 1)); } Path[] files = getOpenFileName(this, "Choose file(s)", rootPath, true, getGraphicsFilters(), 0); if (files != null) { List<String> skippedFiles = new ArrayList<String>(); int idx = listInputFiles.getSelectedIndex() + 1; for (final Path file: files) { if (isValidInput(file)) { modelInputFiles.addElement(file.toString()); idx++; } else { skippedFiles.add(file.toString()); } } listInputFiles.setSelectedIndex(idx - 1); listInputFiles.requestFocus(); updateStatus(); if (!skippedFiles.isEmpty()) { StringBuilder sb = new StringBuilder(); if (skippedFiles.size() == 1) { sb.append(String.format("%1$d file has been skipped:\n", skippedFiles.size())); } else { sb.append(String.format("%1$d files have been skipped:\n", skippedFiles.size())); } for (int i = 0; i < Math.min(5, skippedFiles.size()); i++) { sb.append(String.format(" - %1$s\n", skippedFiles.get(i))); } if (skippedFiles.size() > 5) { sb.append(" - ...\n"); } JOptionPane.showMessageDialog(this, sb.toString(), "Error", JOptionPane.ERROR_MESSAGE); } } } private void inputAddFolder() { Path rootPath = null; if (!modelInputFiles.isEmpty()) { rootPath = FileManager.resolve(modelInputFiles.get(modelInputFiles.size() - 1)); } Path path = getOpenPathName(this, "Choose folder", rootPath); if (path != null && Files.isDirectory(path)) { // adding all files in the directory FileNameExtensionFilter[] filters = getGraphicsFilters(); List<String> skippedFiles = new ArrayList<String>(); int idx = listInputFiles.getSelectedIndex() + 1; try (DirectoryStream<Path> dstream = Files.newDirectoryStream(path)) { for (final Path file: dstream) { for (final FileNameExtensionFilter filter: filters) { if (Files.isRegularFile(file) && filter.accept(file.toFile())) { if (isValidInput(file)) { modelInputFiles.addElement(file.toString()); idx++; } else { skippedFiles.add(file.toString()); } break; } } } } catch (IOException e) { JOptionPane.showMessageDialog(this, "Unable to read files from the specified folder.", "Error", JOptionPane.ERROR_MESSAGE); e.printStackTrace(); return; } listInputFiles.setSelectedIndex(idx - 1); listInputFiles.requestFocus(); updateStatus(); if (!skippedFiles.isEmpty()) { StringBuilder sb = new StringBuilder(); if (skippedFiles.size() == 1) { sb.append(String.format("%1$d file has been skipped:\n", skippedFiles.size())); } else { sb.append(String.format("%1$d files have been skipped:\n", skippedFiles.size())); } for (int i = 0; i < Math.min(5, skippedFiles.size()); i++) { sb.append(String.format(" - %1$s\n", skippedFiles.get(i))); } if (skippedFiles.size() > 5) { sb.append(" - ...\n"); } JOptionPane.showMessageDialog(this, sb.toString(), "Error", JOptionPane.ERROR_MESSAGE); } } } private void inputRemove() { int curIdx = Integer.MAX_VALUE; int indices[] = listInputFiles.getSelectedIndices(); if (indices != null && indices.length > 0) { for (int i = indices.length - 1; i >= 0; i--) { modelInputFiles.remove(indices[i]); if (indices[i] < curIdx) { curIdx = indices[i]; } } curIdx = Math.min(modelInputFiles.size() - 1, curIdx); listInputFiles.setSelectedIndex(curIdx); listInputFiles.requestFocus(); updateStatus(); } } private void inputRemoveAll() { if (!modelInputFiles.isEmpty()) { modelInputFiles.clear(); listInputFiles.requestFocus(); updateStatus(); } } private void setOutput() { Path rootPath = null; if (!tfOutput.getText().isEmpty()) { rootPath = FileManager.resolve(tfOutput.getText()); } Path path = getOpenPathName(this, "Select output directory", rootPath); if (path != null) { tfOutput.setText(path.toString()); updateStatus(); } } private List<String> convert() { List<String> result = new ArrayList<String>(2); final String progressMsg = "Converting file %1$d / %2$d"; int progressIdx = 0, progressMax = modelInputFiles.size() + 1; ProgressMonitor progress = new ProgressMonitor(this, "Converting files...", "Preparing", 0, progressMax); progress.setMillisToDecideToPopup(250); progress.setMillisToPopup(500); progress.setProgress(progressIdx++); int failed = 0, skipped = 0; final String outPath = tfOutput.getText(); try { for (int i = 0; i < modelInputFiles.size(); i++) { if (progress.isCanceled()) { progress.close(); result.add(null); result.add("Conversion cancelled."); return result; } progress.setNote(String.format(progressMsg, progressIdx, modelInputFiles.size())); progress.setProgress(progressIdx++); // 1. prepare data Path inFile = FileManager.resolve(modelInputFiles.get(i)); Path outFile = FileManager.resolve(outPath, StreamUtils.replaceFileExtension(inFile.getFileName().toString(), "BMP")); if (Files.exists(outFile)) { if (cbOverwrite.getSelectedIndex() == 0) { // ask String msg = String.format("File %1$s already exists. Overwrite?", outFile.getFileName()); int ret = JOptionPane.showConfirmDialog(this, msg, "Overwrite?", JOptionPane.YES_NO_CANCEL_OPTION); if (ret == JOptionPane.NO_OPTION) { skipped++; continue; } else if (ret == JOptionPane.CANCEL_OPTION) { progress.close(); result.add(null); result.add("Conversion cancelled."); return result; } } else if (cbOverwrite.getSelectedIndex() == 2) { // skip skipped++; continue; } } Image img = null; try { img = ImageIO.read(inFile.toFile()); } catch (Exception e) { failed++; img = null; } // 2. write BMP output if (img != null && outFile != null) { if (!writeBMP(img, outFile, cbEnableAlpha.isSelected())) { failed++; } } } } finally { progress.close(); progress = null; } // creating summary if (failed+skipped > 0) { if (failed > 0) { result.add(null); } String msg = null; if (failed+skipped == 1) { msg = "1 input file has been skipped."; } else { msg = String.format("%1$d input files have been skipped.", failed+skipped); } result.add(msg); } else { result.add("Conversion finished successfully."); } return result; } // creates a 32-bit BMP files that is compatible with BG(2)EE private boolean writeBMP(Image srcImage, Path file, boolean hasAlpha) { if (srcImage != null && file != null) { BufferedImage image = ColorConvert.toBufferedImage(srcImage, true); // "fixing" premultiplied alpha format if (hasAlpha && cbFixPremultipliedAlpha.isSelected()) { int[] pixels = ((DataBufferInt)image.getRaster().getDataBuffer()).getData(); if (pixels != null) { for (int i = 0; i < pixels.length; i++) { float anorm = (float)((pixels[i] >>> 24) & 0xff) / 255.0f; anorm = (anorm > 0.0f) ? 1.0f / anorm : 0.0f; int r = (int)(((float)((pixels[i] >>> 16) & 0xff) * anorm) + 0.5f); if (r > 255) r = 255; int g = (int)(((float)((pixels[i] >>> 8) & 0xff) * anorm) + 0.5f); if (g > 255) g = 255; int b = (int)(((float)(pixels[i] & 0xff) * anorm) + 0.5f); if (b > 255) b = 255; pixels[i] = (pixels[i] & 0xff000000) | (r << 16) | (g << 8) | b; } } } int bpp = hasAlpha ? 32 : 24; int bytesPerPixel = bpp / 8; int bytesPerLine = image.getWidth()*bytesPerPixel; int fillBytes = (4 - (bytesPerLine & 3)) & 3; // writing BMP header int comression = hasAlpha ? 3 : 0; int sizeFileHeader = 14; int sizeBitmapHeader = hasAlpha ? 124 : 40; int headerSize = sizeFileHeader + sizeBitmapHeader; int fileSize = headerSize + (bytesPerLine + fillBytes)*image.getHeight(); ByteBuffer buffer = StreamUtils.getByteBuffer(headerSize); // file header buffer.put("BM".getBytes()); // File type ("BM") buffer.putInt(fileSize); // total file size buffer.putInt(0); // reserved buffer.putInt(sizeFileHeader+sizeBitmapHeader); // start of pixel data // bitmap header buffer.putInt(sizeBitmapHeader); // bitmap header size buffer.putInt(image.getWidth()); // image width buffer.putInt(image.getHeight()); // image height buffer.putShort((short)1); // color planes buffer.putShort((short)bpp); // bits per pixel buffer.putInt(comression); // compression (0=uncompressed, 3=bitfield) buffer.putInt(image.getWidth()*image.getHeight()*4); // size of bitmap in bytes buffer.putInt(0xb12); // pixels per meter buffer.putInt(0xb12); // pixels per meter buffer.putInt(0); // colors used (palette only) buffer.putInt(0); // important colors (palette only) if (hasAlpha) { buffer.putInt(0x00ff0000); // red bitmask buffer.putInt(0x0000ff00); // green bitmask buffer.putInt(0x000000ff); // blue bitmask buffer.putInt(0xff000000); // alpha bitmask buffer.put("BGRs".getBytes()); // color space type byte[] zero = new byte[16*4]; Arrays.fill(zero, (byte)0); buffer.put(zero); // remaining fields are empty zero = null; } // writing BMP pixel data in ARGB format (upside down) try (OutputStream os = StreamUtils.getOutputStream(file, true)) { // writing header os.write(buffer.array()); // writing pixel data final int transThreshold = 0x20; byte[] row = new byte[bytesPerLine+fillBytes]; int[] pixels = ((DataBufferInt)image.getRaster().getDataBuffer()).getData(); for(int y = image.getHeight() - 1; y >= 0; y--) { for (int i = 0, idx = y*image.getWidth(); i < bytesPerLine; i += bytesPerPixel, idx++) { if (!hasAlpha && (pixels[idx] >>> 24) < transThreshold) { pixels[idx] = 0x00ff00; // transparent pixels are translated into RGB(0, 255, 0) } row[i+0] = (byte)(pixels[idx] & 0xff); row[i+1] = (byte)((pixels[idx] >>> 8) & 0xff); row[i+2] = (byte)((pixels[idx] >>> 16) & 0xff); if (hasAlpha) { row[i+3] = (byte)((pixels[idx] >>> 24) & 0xff); } } // adding alignment bytes for (int i = 0; i < fillBytes; i++) { row[bytesPerLine+i] = (byte)0; } os.write(row); } return true; } catch (Exception e) { e.printStackTrace(); } } return false; } }