From c88b412fd4b0aba01a70b3d229866040676787e7 Mon Sep 17 00:00:00 2001 From: Ben Fry Date: Wed, 9 Oct 2013 00:21:53 -0400 Subject: [PATCH] fix up file selection dialogs --- .../src/processing/app/tools/Chooser.java | 242 ++++++++ .../src/processing/app/tools/MovieMaker.java | 570 +++++++++++------- core/src/processing/core/PApplet.java | 9 +- todo.txt | 4 +- 4 files changed, 608 insertions(+), 217 deletions(-) create mode 100644 build/shared/tools/MovieMaker/src/processing/app/tools/Chooser.java diff --git a/build/shared/tools/MovieMaker/src/processing/app/tools/Chooser.java b/build/shared/tools/MovieMaker/src/processing/app/tools/Chooser.java new file mode 100644 index 000000000..b75ffe138 --- /dev/null +++ b/build/shared/tools/MovieMaker/src/processing/app/tools/Chooser.java @@ -0,0 +1,242 @@ +package processing.app.tools; + +import java.awt.EventQueue; +import java.awt.FileDialog; +import java.awt.Frame; +import java.io.File; + +import javax.swing.JFileChooser; + + +public class Chooser { + static final boolean useNativeSelect = true; + + + static abstract class Callback { + //abstract void select(File file); + void handle(final File file) { + EventQueue.invokeLater(new Runnable() { +// new Thread(new Runnable() { + public void run() { + select(file); + } + }); +// }).start(); + } + + abstract void select(File file); + } + + +// Frame parent; +// +// public Chooser(Frame parent) { +// this.parent = parent; +// } + + /** + * Open a platform-specific file chooser dialog to select a file for input. + * After the selection is made, the selected File will be passed to the + * 'callback' function. If the dialog is closed or canceled, null will be + * sent to the function, so that the program is not waiting for additional + * input. The callback is necessary because of how threading works. + * + *
+   * void setup() {
+   *   selectInput("Select a file to process:", "fileSelected");
+   * }
+   *
+   * void fileSelected(File selection) {
+   *   if (selection == null) {
+   *     println("Window was closed or the user hit cancel.");
+   *   } else {
+   *     println("User selected " + fileSeleted.getAbsolutePath());
+   *   }
+   * }
+   * 
+ * + * For advanced users, the method must be 'public', which is true for all + * methods inside a sketch when run from the PDE, but must explicitly be + * set when using Eclipse or other development environments. + * + * @webref input:files + * @param prompt message to the user + * @param callback name of the method to be called when the selection is made + */ +// public void selectInput(String prompt, String callback) { +// selectInput(prompt, callback, null); +// } + + +// public void selectInput(String prompt, String callback, File file) { +// selectInput(prompt, callback, file, this); +// } + + +// public void selectInput(String prompt, String callback, +// File file, Object callbackObject) { +// selectInput(prompt, callback, file, callbackObject, selectFrame()); +// } + + + static public void selectInput(Frame parent, String prompt, File file, + Callback callback) { + selectImpl(parent, prompt, file, callback, FileDialog.LOAD); + } + + + /** + * See selectInput() for details. + * + * @webref output:files + * @param prompt message to the user + * @param callback name of the method to be called when the selection is made + */ +// public void selectOutput(String prompt, String callback) { +// selectOutput(prompt, callback, null); +// } +// +// public void selectOutput(String prompt, String callback, File file) { +// selectOutput(prompt, callback, file, this); +// } +// +// +// public void selectOutput(String prompt, String callback, +// File file, Object callbackObject) { +// selectOutput(prompt, callback, file, callbackObject, selectFrame()); +// } + + + static public void selectOutput(Frame parent, String prompt, File file, + Callback callback) { + selectImpl(parent, prompt, file, callback, FileDialog.SAVE); + } + + + static protected void selectImpl(final Frame parentFrame, + final String prompt, + final File defaultSelection, + final Callback callback, + final int mode) { +// EventQueue.invokeLater(new Runnable() { +// public void run() { + File selectedFile = null; + + if (useNativeSelect) { + FileDialog dialog = new FileDialog(parentFrame, prompt, mode); + if (defaultSelection != null) { + dialog.setDirectory(defaultSelection.getParent()); + dialog.setFile(defaultSelection.getName()); + } + dialog.setVisible(true); + String directory = dialog.getDirectory(); + String filename = dialog.getFile(); + if (filename != null) { + selectedFile = new File(directory, filename); + } + + } else { + JFileChooser chooser = new JFileChooser(); + chooser.setDialogTitle(prompt); + if (defaultSelection != null) { + chooser.setSelectedFile(defaultSelection); + } + + int result = -1; + if (mode == FileDialog.SAVE) { + result = chooser.showSaveDialog(parentFrame); + } else if (mode == FileDialog.LOAD) { + result = chooser.showOpenDialog(parentFrame); + } + if (result == JFileChooser.APPROVE_OPTION) { + selectedFile = chooser.getSelectedFile(); + } + } + //selectCallback(selectedFile, callbackMethod, callbackObject); + callback.handle(selectedFile); +// } +// }); + } + + + /** + * See selectInput() for details. + * + * @webref input:files + * @param prompt message to the user + * @param callback name of the method to be called when the selection is made + */ +// public void selectFolder(String prompt, String callback) { +// selectFolder(prompt, callback, null); +// } +// +// +// public void selectFolder(String prompt, String callback, File file) { +// selectFolder(prompt, callback, file, this); +// } +// +// +// public void selectFolder(String prompt, String callback, +// File file, Object callbackObject) { +// selectFolder(prompt, callback, file, callbackObject, selectFrame()); +// } + + + static public void selectFolder(final Frame parentFrame, + final String prompt, + final File defaultSelection, + final Callback callback) { +// EventQueue.invokeLater(new Runnable() { +// public void run() { + File selectedFile = null; + + if (System.getProperty("os.name").contains("Mac") && useNativeSelect) { + FileDialog fileDialog = + new FileDialog(parentFrame, prompt, FileDialog.LOAD); + System.setProperty("apple.awt.fileDialogForDirectories", "true"); + fileDialog.setVisible(true); + System.setProperty("apple.awt.fileDialogForDirectories", "false"); + String filename = fileDialog.getFile(); + if (filename != null) { + selectedFile = new File(fileDialog.getDirectory(), fileDialog.getFile()); + } + } else { + JFileChooser fileChooser = new JFileChooser(); + fileChooser.setDialogTitle(prompt); + fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + if (defaultSelection != null) { + fileChooser.setSelectedFile(defaultSelection); + } + + int result = fileChooser.showOpenDialog(parentFrame); + if (result == JFileChooser.APPROVE_OPTION) { + selectedFile = fileChooser.getSelectedFile(); + } + } + //selectCallback(selectedFile, callbackMethod, callbackObject); + callback.handle(selectedFile); +// } +// }); + } + + +// static private void selectCallback(File selectedFile, +// String callbackMethod, +// Object callbackObject) { +// try { +// Class callbackClass = callbackObject.getClass(); +// Method selectMethod = +// callbackClass.getMethod(callbackMethod, new Class[] { File.class }); +// selectMethod.invoke(callbackObject, new Object[] { selectedFile }); +// +// } catch (IllegalAccessException iae) { +// System.err.println(callbackMethod + "() must be public"); +// +// } catch (InvocationTargetException ite) { +// ite.printStackTrace(); +// +// } catch (NoSuchMethodException nsme) { +// System.err.println(callbackMethod + "() could not be found"); +// } +// } +} \ No newline at end of file diff --git a/build/shared/tools/MovieMaker/src/processing/app/tools/MovieMaker.java b/build/shared/tools/MovieMaker/src/processing/app/tools/MovieMaker.java index 5378dea18..7a37ea60f 100644 --- a/build/shared/tools/MovieMaker/src/processing/app/tools/MovieMaker.java +++ b/build/shared/tools/MovieMaker/src/processing/app/tools/MovieMaker.java @@ -11,10 +11,6 @@ package processing.app.tools; * For details see accompanying license terms. */ -import ch.randelshofer.gui.datatransfer.FileTextFieldTransferHandler; -import ch.randelshofer.media.mp3.MP3AudioInputStream; -import ch.randelshofer.media.quicktime.QuickTimeWriter; - import java.awt.Font; import java.awt.Graphics2D; import java.awt.RenderingHints; @@ -40,11 +36,30 @@ import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.UnsupportedAudioFileException; -import javax.swing.*; +import javax.swing.DefaultComboBoxModel; +import javax.swing.GroupLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JRootPane; +import javax.swing.JTextField; +import javax.swing.KeyStroke; +import javax.swing.LayoutStyle; +import javax.swing.ProgressMonitor; +import javax.swing.SwingWorker; +import javax.swing.WindowConstants; import javax.swing.border.EmptyBorder; import javax.swing.filechooser.FileSystemView; import processing.app.Editor; +import ch.randelshofer.gui.datatransfer.FileTextFieldTransferHandler; +import ch.randelshofer.media.mp3.MP3AudioInputStream; +import ch.randelshofer.media.quicktime.QuickTimeWriter; // TODO [fry 2011-09-06] @@ -63,9 +78,9 @@ import processing.app.Editor; * can be found here. */ public class MovieMaker extends JFrame implements Tool { - private JFileChooser imageFolderChooser; - private JFileChooser soundFileChooser; - private JFileChooser movieFileChooser; +// private JFileChooser imageFolderChooser; +// private JFileChooser soundFileChooser; +// private JFileChooser movieFileChooser; private Preferences prefs; // private Editor editor; @@ -213,7 +228,7 @@ public class MovieMaker extends JFrame implements Tool { // fastStartRadio = new JRadioButton(); // fastStartCompressedRadio = new JRadioButton(); - FormListener formListener = new FormListener(); +// FormListener formListener = new FormListener(); setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { @@ -250,14 +265,72 @@ public class MovieMaker extends JFrame implements Tool { imageFolderHelpLabel.setText("Drag a folder with image files into the field below:"); chooseImageFolderButton.setText("Choose..."); - chooseImageFolderButton.addActionListener(formListener); + //chooseImageFolderButton.addActionListener(formListener); + chooseImageFolderButton.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + Chooser.selectFolder(MovieMaker.this, + "Select image folder...", + new File(imageFolderField.getText()), + new Chooser.Callback() { + void select(File file) { + if (file != null) { + imageFolderField.setText(file.getAbsolutePath()); + } + } + }); + } + }); + soundFileHelpLabel.setText("Drag a sound file into the field below (.au, .aiff, .wav, .mp3):"); chooseSoundFileButton.setText("Choose..."); - chooseSoundFileButton.addActionListener(formListener); + //chooseSoundFileButton.addActionListener(formListener); + chooseSoundFileButton.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + Chooser.selectInput(MovieMaker.this, + "Select sound file...", + new File(soundFileField.getText()), + new Chooser.Callback() { + + void select(File file) { + if (file != null) { + soundFileField.setText(file.getAbsolutePath()); + } + } + }); + } + }); createMovieButton.setText("Create Movie..."); - createMovieButton.addActionListener(formListener); +// createMovieButton.addActionListener(formListener); + createMovieButton.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + String lastPath = prefs.get("movie.outputFile", null); + File lastFile = lastPath == null ? null : new File(lastPath); + Chooser.selectOutput(MovieMaker.this, + "Save movie as...", + lastFile, + new Chooser.Callback() { + @Override + void select(File file) { + if (file != null) { + String path = file.getAbsolutePath(); + if (!path.toLowerCase().endsWith(".mov")) { + path += ".mov"; + } + prefs.put("movie.outputFile", path); + createMovie(new File(path)); + } + } + }); + } + }); Font font = new Font("Dialog", Font.PLAIN, 11); @@ -394,58 +467,58 @@ public class MovieMaker extends JFrame implements Tool { // Code for dispatching events from components to event handlers. - private class FormListener implements java.awt.event.ActionListener { - FormListener() {} - public void actionPerformed(java.awt.event.ActionEvent evt) { - if (evt.getSource() == chooseImageFolderButton) { - MovieMaker.this.chooseImageFolder(evt); - } - else if (evt.getSource() == chooseSoundFileButton) { - MovieMaker.this.chooseSoundFile(evt); - } - else if (evt.getSource() == createMovieButton) { - MovieMaker.this.createMovie(evt); - } -// else if (evt.getSource() == fastStartCompressedRadio) { -// MovieMaker.this.streamingRadioPerformed(evt); +// private class FormListener implements java.awt.event.ActionListener { +// FormListener() {} +// public void actionPerformed(java.awt.event.ActionEvent evt) { +// if (evt.getSource() == chooseImageFolderButton) { +// MovieMaker.this.chooseImageFolder(evt); // } -// else if (evt.getSource() == fastStartRadio) { -// MovieMaker.this.streamingRadioPerformed(evt); +// else if (evt.getSource() == chooseSoundFileButton) { +// MovieMaker.this.chooseSoundFile(evt); // } -// else if (evt.getSource() == noPreparationRadio) { -// MovieMaker.this.streamingRadioPerformed(evt); +// else if (evt.getSource() == createMovieButton) { +// MovieMaker.this.createMovie(evt); // } - } - } +//// else if (evt.getSource() == fastStartCompressedRadio) { +//// MovieMaker.this.streamingRadioPerformed(evt); +//// } +//// else if (evt.getSource() == fastStartRadio) { +//// MovieMaker.this.streamingRadioPerformed(evt); +//// } +//// else if (evt.getSource() == noPreparationRadio) { +//// MovieMaker.this.streamingRadioPerformed(evt); +//// } +// } +// } - private void chooseImageFolder(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chooseImageFolder - if (imageFolderChooser == null) { - imageFolderChooser = new JFileChooser(); - imageFolderChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - if (imageFolderField.getText().length() > 0) { - imageFolderChooser.setSelectedFile(new File(imageFolderField.getText())); - } else if (soundFileField.getText().length() > 0) { - imageFolderChooser.setCurrentDirectory(new File(soundFileField.getText()).getParentFile()); - } - } - if (JFileChooser.APPROVE_OPTION == imageFolderChooser.showOpenDialog(this)) { - imageFolderField.setText(imageFolderChooser.getSelectedFile().getPath()); - } - } - - private void chooseSoundFile(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chooseSoundFile - if (soundFileChooser == null) { - soundFileChooser = new JFileChooser(); - if (soundFileField.getText().length() > 0) { - soundFileChooser.setSelectedFile(new File(soundFileField.getText())); - } else if (imageFolderField.getText().length() > 0) { - soundFileChooser.setCurrentDirectory(new File(imageFolderField.getText())); - } - } - if (JFileChooser.APPROVE_OPTION == soundFileChooser.showOpenDialog(this)) { - soundFileField.setText(soundFileChooser.getSelectedFile().getPath()); - } - } +// private void chooseImageFolder(ActionEvent evt) { +// if (imageFolderChooser == null) { +// imageFolderChooser = new JFileChooser(); +// imageFolderChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); +// if (imageFolderField.getText().length() > 0) { +// imageFolderChooser.setSelectedFile(new File(imageFolderField.getText())); +// } else if (soundFileField.getText().length() > 0) { +// imageFolderChooser.setCurrentDirectory(new File(soundFileField.getText()).getParentFile()); +// } +// } +// if (JFileChooser.APPROVE_OPTION == imageFolderChooser.showOpenDialog(this)) { +// imageFolderField.setText(imageFolderChooser.getSelectedFile().getPath()); +// } +// } +// +// private void chooseSoundFile(ActionEvent evt) { +// if (soundFileChooser == null) { +// soundFileChooser = new JFileChooser(); +// if (soundFileField.getText().length() > 0) { +// soundFileChooser.setSelectedFile(new File(soundFileField.getText())); +// } else if (imageFolderField.getText().length() > 0) { +// soundFileChooser.setCurrentDirectory(new File(imageFolderField.getText())); +// } +// } +// if (JFileChooser.APPROVE_OPTION == soundFileChooser.showOpenDialog(this)) { +// soundFileField.setText(soundFileChooser.getSelectedFile().getPath()); +// } +// } // this is super naughty, and shouldn't be out here. it's a hack to get the @@ -453,7 +526,9 @@ public class MovieMaker extends JFrame implements Tool { // given a bit of time. you know, time? the infinite but non-renewable resource? int width, height; - private void createMovie(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_createMovie + private void createMovie(final File movieFile) { + createMovieButton.setEnabled(false); + // --------------------------------- // Check input // --------------------------------- @@ -512,6 +587,7 @@ public class MovieMaker extends JFrame implements Tool { // --------------------------------- // Choose an output file // --------------------------------- + /* if (movieFileChooser == null) { movieFileChooser = new JFileChooser(); if (prefs.get("movie.outputFile", null) != null) { @@ -533,6 +609,7 @@ public class MovieMaker extends JFrame implements Tool { : new File(movieFileChooser.getSelectedFile().getPath() + ".mov"); prefs.put("movie.outputFile", movieFile.getPath()); createMovieButton.setEnabled(false); + */ final boolean originalSize = originalSizeCheckBox.isSelected(); @@ -668,29 +745,26 @@ public class MovieMaker extends JFrame implements Tool { /** - * Targa image loader for RLE-compressed TGA files. - *

- * Rewritten for 0115 to read/write RLE-encoded targa images. - * For 0125, non-RLE encoded images are now supported, along with - * images whose y-order is reversed (which is standard for TGA files). + * Targa image loader for RLE-compressed TGA files. + * Code taken from PApplet, any changes here should lead to updates there. */ static protected BufferedImage loadImageTGA(File file) throws IOException { InputStream is = new FileInputStream(file); - if (is == null) return null; - - byte header[] = new byte[18]; - int offset = 0; - do { - int count = is.read(header, offset, header.length - offset); - if (count == -1) return null; - offset += count; - } while (offset < 18); + + try { + byte header[] = new byte[18]; + int offset = 0; + do { + int count = is.read(header, offset, header.length - offset); + if (count == -1) return null; + offset += count; + } while (offset < 18); /* header[2] image type code 2 (0x02) - Uncompressed, RGB images. 3 (0x03) - Uncompressed, black and white images. - 10 (0x0A) - Runlength encoded RGB images. + 10 (0x0A) - Run-length encoded RGB images. 11 (0x0B) - Compressed, black and white images. (grayscale?) header[16] is the bit depth (8, 24, 32) @@ -703,167 +777,235 @@ public class MovieMaker extends JFrame implements Tool { 128 64 32 16 8 4 2 1 */ - int format = 0; - final int RGB = 1; - final int ARGB = 2; - final int ALPHA = 4; + int format = 0; + final int RGB = 1; + final int ARGB = 2; + final int ALPHA = 4; - if (((header[2] == 3) || (header[2] == 11)) && // B&W, plus RLE or not - (header[16] == 8) && // 8 bits - ((header[17] == 0x8) || (header[17] == 0x28))) { // origin, 32 bit - format = ALPHA; + if (((header[2] == 3) || (header[2] == 11)) && // B&W, plus RLE or not + (header[16] == 8) && // 8 bits + ((header[17] == 0x8) || (header[17] == 0x28))) { // origin, 32 bit + format = ALPHA; - } else if (((header[2] == 2) || (header[2] == 10)) && // RGB, RLE or not - (header[16] == 24) && // 24 bits - ((header[17] == 0x20) || (header[17] == 0))) { // origin - format = RGB; + } else if (((header[2] == 2) || (header[2] == 10)) && // RGB, RLE or not + (header[16] == 24) && // 24 bits + ((header[17] == 0x20) || (header[17] == 0))) { // origin + format = RGB; - } else if (((header[2] == 2) || (header[2] == 10)) && - (header[16] == 32) && - ((header[17] == 0x8) || (header[17] == 0x28))) { // origin, 32 - format = ARGB; - } - - if (format == 0) { - throw new IOException("Unknown .tga file format for " + file.getName()); - } - - int w = ((header[13] & 0xff) << 8) + (header[12] & 0xff); - int h = ((header[15] & 0xff) << 8) + (header[14] & 0xff); - //PImage outgoing = createImage(w, h, format); - int[] pixels = new int[w * h]; - - // where "reversed" means upper-left corner (normal for most of - // the modernized world, but "reversed" for the tga spec) - //boolean reversed = (header[17] & 0x20) != 0; - // https://github.com/processing/processing/issues/1682 - boolean reversed = (header[17] & 0x20) == 0; - - if ((header[2] == 2) || (header[2] == 3)) { // not RLE encoded - if (reversed) { - int index = (h-1) * w; - switch (format) { - case ALPHA: - for (int y = h-1; y >= 0; y--) { - for (int x = 0; x < w; x++) { - pixels[index + x] = is.read(); - } - index -= w; - } - break; - case RGB: - for (int y = h-1; y >= 0; y--) { - for (int x = 0; x < w; x++) { - pixels[index + x] = - is.read() | (is.read() << 8) | (is.read() << 16) | - 0xff000000; - } - index -= w; - } - break; - case ARGB: - for (int y = h-1; y >= 0; y--) { - for (int x = 0; x < w; x++) { - pixels[index + x] = - is.read() | (is.read() << 8) | (is.read() << 16) | - (is.read() << 24); - } - index -= w; - } - } - } else { // not reversed - int count = w * h; - switch (format) { - case ALPHA: - for (int i = 0; i < count; i++) { - pixels[i] = is.read(); - } - break; - case RGB: - for (int i = 0; i < count; i++) { - pixels[i] = - is.read() | (is.read() << 8) | (is.read() << 16) | - 0xff000000; - } - break; - case ARGB: - for (int i = 0; i < count; i++) { - pixels[i] = - is.read() | (is.read() << 8) | (is.read() << 16) | - (is.read() << 24); - } - break; - } + } else if (((header[2] == 2) || (header[2] == 10)) && + (header[16] == 32) && + ((header[17] == 0x8) || (header[17] == 0x28))) { // origin, 32 + format = ARGB; } - } else { // header[2] is 10 or 11 - int index = 0; + if (format == 0) { + throw new IOException("Unknown .tga file format for " + file.getName()); + } - while (index < pixels.length) { - int num = is.read(); - boolean isRLE = (num & 0x80) != 0; - if (isRLE) { - num -= 127; // (num & 0x7F) + 1 - int pixel = 0; + int w = ((header[13] & 0xff) << 8) + (header[12] & 0xff); + int h = ((header[15] & 0xff) << 8) + (header[14] & 0xff); + //PImage outgoing = createImage(w, h, format); + int[] pixels = new int[w * h]; + + // where "reversed" means upper-left corner (normal for most of + // the modernized world, but "reversed" for the tga spec) + //boolean reversed = (header[17] & 0x20) != 0; + // https://github.com/processing/processing/issues/1682 + boolean reversed = (header[17] & 0x20) == 0; + + if ((header[2] == 2) || (header[2] == 3)) { // not RLE encoded + if (reversed) { + int index = (h-1) * w; switch (format) { case ALPHA: - pixel = is.read(); + for (int y = h-1; y >= 0; y--) { + for (int x = 0; x < w; x++) { + pixels[index + x] = is.read(); + } + index -= w; + } break; case RGB: - pixel = 0xFF000000 | + for (int y = h-1; y >= 0; y--) { + for (int x = 0; x < w; x++) { + pixels[index + x] = + is.read() | (is.read() << 8) | (is.read() << 16) | + 0xff000000; + } + index -= w; + } + break; + case ARGB: + for (int y = h-1; y >= 0; y--) { + for (int x = 0; x < w; x++) { + pixels[index + x] = + is.read() | (is.read() << 8) | (is.read() << 16) | + (is.read() << 24); + } + index -= w; + } + } + } else { // not reversed + int count = w * h; + switch (format) { + case ALPHA: + for (int i = 0; i < count; i++) { + pixels[i] = is.read(); + } + break; + case RGB: + for (int i = 0; i < count; i++) { + pixels[i] = + is.read() | (is.read() << 8) | (is.read() << 16) | + 0xff000000; + } + break; + case ARGB: + for (int i = 0; i < count; i++) { + pixels[i] = + is.read() | (is.read() << 8) | (is.read() << 16) | + (is.read() << 24); + } + break; + } + } + + } else { // header[2] is 10 or 11 + int index = 0; + + while (index < pixels.length) { + int num = is.read(); + boolean isRLE = (num & 0x80) != 0; + if (isRLE) { + num -= 127; // (num & 0x7F) + 1 + int pixel = 0; + switch (format) { + case ALPHA: + pixel = is.read(); + break; + case RGB: + pixel = 0xFF000000 | is.read() | (is.read() << 8) | (is.read() << 16); - //(is.read() << 16) | (is.read() << 8) | is.read(); - break; - case ARGB: - pixel = is.read() | + //(is.read() << 16) | (is.read() << 8) | is.read(); + break; + case ARGB: + pixel = is.read() | (is.read() << 8) | (is.read() << 16) | (is.read() << 24); - break; - } - for (int i = 0; i < num; i++) { - pixels[index++] = pixel; - if (index == pixels.length) break; - } - } else { // write up to 127 bytes as uncompressed - num += 1; - switch (format) { - case ALPHA: - for (int i = 0; i < num; i++) { - pixels[index++] = is.read(); + break; } - break; - case RGB: for (int i = 0; i < num; i++) { - pixels[index++] = 0xFF000000 | - is.read() | (is.read() << 8) | (is.read() << 16); + pixels[index++] = pixel; + if (index == pixels.length) break; } - break; - case ARGB: - for (int i = 0; i < num; i++) { - pixels[index++] = is.read() | - (is.read() << 8) | (is.read() << 16) | (is.read() << 24); + } else { // write up to 127 bytes as uncompressed + num += 1; + switch (format) { + case ALPHA: + for (int i = 0; i < num; i++) { + pixels[index++] = is.read(); + } + break; + case RGB: + for (int i = 0; i < num; i++) { + pixels[index++] = 0xFF000000 | + is.read() | (is.read() << 8) | (is.read() << 16); + } + break; + case ARGB: + for (int i = 0; i < num; i++) { + pixels[index++] = is.read() | + (is.read() << 8) | (is.read() << 16) | (is.read() << 24); + } + break; } - break; } } + + if (!reversed) { + int[] temp = new int[w]; + for (int y = 0; y < h/2; y++) { + int z = (h-1) - y; + System.arraycopy(pixels, y*w, temp, 0, w); + System.arraycopy(pixels, z*w, pixels, y*w, w); + System.arraycopy(temp, 0, pixels, z*w, w); + } + } + } + is.close(); + int type = (format == RGB) ? + BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB; + BufferedImage image = new BufferedImage(w, h, type); + WritableRaster wr = image.getRaster(); + wr.setDataElements(0, 0, w, h, pixels); + return image; + + } finally { + is.close(); + } + } + + + /* + static public void selectFolder(final Frame parentFrame, + final String prompt, +// final String callbackMethod, + final File defaultSelection, +// final Object callbackObject, + final SelectCallback callback) { +// EventQueue.invokeLater(new Runnable() { +// public void run() { + File selectedFile = null; + + if (System.getProperty("os.name").contains("Mac")) { + FileDialog fileDialog = + new FileDialog(parentFrame, prompt, FileDialog.LOAD); + System.setProperty("apple.awt.fileDialogForDirectories", "true"); + fileDialog.setVisible(true); + System.setProperty("apple.awt.fileDialogForDirectories", "false"); + String filename = fileDialog.getFile(); + if (filename != null) { + selectedFile = new File(fileDialog.getDirectory(), fileDialog.getFile()); + } + } else { + JFileChooser fileChooser = new JFileChooser(); + fileChooser.setDialogTitle(prompt); + fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + if (defaultSelection != null) { + fileChooser.setSelectedFile(defaultSelection); } - if (!reversed) { - int[] temp = new int[w]; - for (int y = 0; y < h/2; y++) { - int z = (h-1) - y; - System.arraycopy(pixels, y*w, temp, 0, w); - System.arraycopy(pixels, z*w, pixels, y*w, w); - System.arraycopy(temp, 0, pixels, z*w, w); - } + int result = fileChooser.showOpenDialog(parentFrame); + if (result == JFileChooser.APPROVE_OPTION) { + selectedFile = fileChooser.getSelectedFile(); } } - int type = (format == RGB) ? - BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB; - BufferedImage image = new BufferedImage(w, h, type); - WritableRaster wr = image.getRaster(); - wr.setDataElements(0, 0, w, h, pixels); - return image; + //selectCallback(selectedFile, callbackMethod, callbackObject); + callback.select(selectedFile); +// } +// }); } + */ + + +// static private void selectCallback(File selectedFile, +// String callbackMethod, +// Object callbackObject) { +// try { +// Class callbackClass = callbackObject.getClass(); +// Method selectMethod = +// callbackClass.getMethod(callbackMethod, new Class[] { File.class }); +// selectMethod.invoke(callbackObject, new Object[] { selectedFile }); +// +// } catch (IllegalAccessException iae) { +// System.err.println(callbackMethod + "() must be public"); +// +// } catch (InvocationTargetException ite) { +// ite.printStackTrace(); +// +// } catch (NoSuchMethodException nsme) { +// System.err.println(callbackMethod + "() could not be found"); +// } +// } // private void streamingRadioPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_streamingRadioPerformed diff --git a/core/src/processing/core/PApplet.java b/core/src/processing/core/PApplet.java index 71c85020b..652c7e862 100755 --- a/core/src/processing/core/PApplet.java +++ b/core/src/processing/core/PApplet.java @@ -5864,6 +5864,13 @@ public class PApplet extends Applet * Rewritten for 0115 to read/write RLE-encoded targa images. * For 0125, non-RLE encoded images are now supported, along with * images whose y-order is reversed (which is standard for TGA files). + *

+ * A version of this function is in MovieMaker.java. Any fixes here + * should be applied over in MovieMaker as well. + *

+ * Known issue with RLE encoding and odd behavior in some apps: + * https://github.com/processing/processing/issues/2096 + * Please help! */ protected PImage loadImageTGA(String filename) throws IOException { InputStream is = createInput(filename); @@ -5881,7 +5888,7 @@ public class PApplet extends Applet header[2] image type code 2 (0x02) - Uncompressed, RGB images. 3 (0x03) - Uncompressed, black and white images. - 10 (0x0A) - Runlength encoded RGB images. + 10 (0x0A) - Run-length encoded RGB images. 11 (0x0B) - Compressed, black and white images. (grayscale?) header[16] is the bit depth (8, 24, 32) diff --git a/todo.txt b/todo.txt index 6c3e834ea..88d0d6dc0 100644 --- a/todo.txt +++ b/todo.txt @@ -26,8 +26,8 @@ o move Movie Maker out to its own separate tool package (with separate build) X http://code.google.com/p/processing/issues/detail?id=837 X https://github.com/processing/processing/issues/875 X basically done in more recent releases - -_ fix file selection dialog (just import PApplet) with MovieMaker +X fix file selection dialog with MovieMaker +X copied from PApplet, but not importing PApplet X add appbundler.jar, otherwise folks have to include Xcode _ if they want to build appbundler, they'll need Xcode