package com.marginallyclever.makelangeloRobot.loadAndSave; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; import java.util.HashSet; import java.util.Iterator; import java.util.Objects; import java.util.ServiceLoader; import java.util.Set; import java.util.prefs.Preferences; import javax.imageio.ImageIO; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.ProgressMonitor; import javax.swing.SwingWorker; import javax.swing.filechooser.FileNameExtensionFilter; import com.marginallyclever.makelangelo.Log; import com.marginallyclever.makelangeloRobot.TransformedImage; import com.marginallyclever.makelangelo.Translator; import com.marginallyclever.makelangeloRobot.ImageManipulator; import com.marginallyclever.makelangeloRobot.MakelangeloRobot; import com.marginallyclever.makelangeloRobot.MakelangeloRobotPanel; import com.marginallyclever.makelangeloRobot.converters.ImageConverter; import com.marginallyclever.makelangeloRobot.generators.Generator_Text; import com.marginallyclever.util.PreferencesHelper; /** * LoadImage uses an InputStream of data to create gcode. * @author Dan Royer * */ public class LoadAndSaveImage extends ImageManipulator implements LoadAndSaveFileType { @SuppressWarnings("deprecation") private Preferences prefs = PreferencesHelper .getPreferenceNode(PreferencesHelper.MakelangeloPreferenceKey.LEGACY_MAKELANGELO_ROOT); private ServiceLoader<ImageConverter> converters; private ImageConverter chosenConverter; private TransformedImage img; private MakelangeloRobot chosenRobot; JPanel conversionPanel; JComboBox<String> conversionOptions; private JPanel converterOptionsContainer; /** * Set of image file extensions. */ private static final Set<String> IMAGE_FILE_EXTENSIONS; static { IMAGE_FILE_EXTENSIONS = new HashSet<>(); IMAGE_FILE_EXTENSIONS.add("jpg"); IMAGE_FILE_EXTENSIONS.add("jpeg"); IMAGE_FILE_EXTENSIONS.add("png"); IMAGE_FILE_EXTENSIONS.add("wbmp"); IMAGE_FILE_EXTENSIONS.add("bmp"); IMAGE_FILE_EXTENSIONS.add("gif"); } private static FileNameExtensionFilter filter = new FileNameExtensionFilter(Translator.get("FileTypeImage"), IMAGE_FILE_EXTENSIONS.toArray(new String[IMAGE_FILE_EXTENSIONS.size()])); private String[] imageConverterNames; public LoadAndSaveImage() { converters = ServiceLoader.load(ImageConverter.class); Iterator<ImageConverter> ici = converters.iterator(); int i=0; while(ici.hasNext()) { ici.next(); i++; } imageConverterNames = new String[i]; i=0; ici = converters.iterator(); while (ici.hasNext()) { ImageManipulator f = ici.next(); imageConverterNames[i++] = f.getName(); } } @Override public FileNameExtensionFilter getFileNameFilter() { return filter; } @Override public boolean canLoad(String filename) { final String filenameExtension = filename.substring(filename.lastIndexOf('.') + 1); return IMAGE_FILE_EXTENSIONS.contains(filenameExtension.toLowerCase()); } protected boolean chooseImageConversionOptions(MakelangeloRobot robot) { conversionPanel = new JPanel(new GridBagLayout()); conversionOptions = new JComboBox<String>(imageConverterNames); converterOptionsContainer = new JPanel(); converterOptionsContainer.setPreferredSize(new Dimension(450,300)); conversionOptions.setSelectedIndex(getPreferredDrawStyle()); conversionOptions.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { changeConverter(conversionOptions,robot); } }); GridBagConstraints c = new GridBagConstraints(); int y = 0; c.anchor = GridBagConstraints.EAST; c.gridwidth = 1; c.gridx = 0; c.gridy = y; conversionPanel.add(new JLabel(Translator.get("ConversionStyle")), c); c.anchor = GridBagConstraints.WEST; c.gridwidth = 3; c.gridx = 1; c.gridy = y++; conversionPanel.add(conversionOptions, c); c.anchor=GridBagConstraints.NORTH; c.fill = GridBagConstraints.HORIZONTAL; c.gridwidth=4; c.gridx=0; c.gridy=y++; c.insets = new Insets(10, 0, 0, 0); converterOptionsContainer.setPreferredSize(new Dimension(449,325)); //previewPane.setBorder(BorderFactory.createLineBorder(new Color(255,0,0))); conversionPanel.add(converterOptionsContainer,c); changeConverter(conversionOptions,robot); int result = JOptionPane.showConfirmDialog(robot.getControlPanel(), conversionPanel, Translator.get("ConversionOptions"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); if (result == JOptionPane.OK_OPTION) { stopSwingWorker(); setPreferredDrawStyle(conversionOptions.getSelectedIndex()); robot.getSettings().saveConfig(); return true; } return false; } private void changeConverter(JComboBox<String> options,MakelangeloRobot robot) { //System.out.println("Changing converter"); stopSwingWorker(); chosenConverter = getConverter(options.getSelectedIndex()); chosenConverter.setLoadAndSave(this); JPanel p = chosenConverter.getPanel(); converterOptionsContainer.removeAll(); if(p!=null) { //System.out.println("Adding panel"); converterOptionsContainer.add(p); converterOptionsContainer.invalidate(); } converterOptionsContainer.getParent().validate(); converterOptionsContainer.getParent().repaint(); createSwingWorker(); } public void reconvert() { changeConverter(conversionOptions,chosenRobot); } private ImageConverter getConverter(int arg0) throws IndexOutOfBoundsException { Iterator<ImageConverter> ici = converters.iterator(); int i=0; while(ici.hasNext()) { ImageConverter chosenConverter = ici.next(); if(i==arg0) { return chosenConverter; } i++; } throw new IndexOutOfBoundsException(); } /** * Load and convert the image in the chosen style * @return false if loading cancelled or failed. */ public boolean load(InputStream in,MakelangeloRobot robot) { try { img = new TransformedImage( ImageIO.read(in) ); } catch (IOException e1) { e1.printStackTrace(); return false; } // scale image to fit paper, same behaviour as before. if( robot.getSettings().getPaperWidth() > robot.getSettings().getPaperHeight() ) { if(robot.getSettings().getPaperWidth()*10.0f < img.getSourceImage().getWidth()) { float f = (float)( robot.getSettings().getPaperWidth()*10.0f / img.getSourceImage().getWidth() ); img.setScaleX(img.getScaleX() * f); img.setScaleY(img.getScaleY() * f); } } else { if(robot.getSettings().getPaperHeight()*10.0f < img.getSourceImage().getHeight()) { float f = (float)( robot.getSettings().getPaperHeight()*10.0f / img.getSourceImage().getHeight() ); img.setScaleX(img.getScaleX() * f); img.setScaleY(img.getScaleY() * f); } } pm = new ProgressMonitor(null, Translator.get("Converting"), "", 0, 100); pm.setProgress(0); pm.setMillisToPopup(0); chosenRobot = robot; chooseImageConversionOptions(robot); return true; } void stopSwingWorker() { if(chosenConverter!=null) { chosenConverter.stopIterating(); } if(swingWorker!=null) { //System.out.println("Stopping swingWorker"); if(swingWorker.cancel(true)) { System.out.println("stopped OK"); } else { System.out.println("stop FAILED"); } } } void createSwingWorker() { //System.out.println("Starting swingWorker"); chosenConverter.setProgressMonitor(pm); chosenConverter.setRobot(chosenRobot); chosenConverter.setImage(img); chosenRobot.setDecorator(chosenConverter); swingWorker = new SwingWorker<Void, Void>() { @Override public Void doInBackground() { chosenConverter.setSwingWorker(swingWorker); // where to save temp output file? // FIXME going through temp files every time may be thrashing SSDs. File tempFile; try { tempFile = File.createTempFile("gcode", ".ngc"); } catch (Exception e) { e.printStackTrace(); return null; } tempFile.deleteOnExit(); // read in image Log.message(Translator.get("Converting") + " " + tempFile.getName()); // convert with style try (OutputStream fileOutputStream = new FileOutputStream(tempFile); Writer out = new OutputStreamWriter(fileOutputStream, StandardCharsets.UTF_8)) { while(chosenConverter.iterate()) { Thread.sleep(5); } chosenConverter.finish(out); chosenRobot.setDecorator(null); if (chosenRobot.getSettings().shouldSignName()) { // Sign name Generator_Text ymh = new Generator_Text(); ymh.setRobot(chosenRobot); ymh.signName(out); } } catch (Exception e) { Log.error(Translator.get("Failed") + e.getLocalizedMessage()); chosenRobot.setDecorator(null); } // out closed when scope of try() ended. LoadAndSaveGCode loader = new LoadAndSaveGCode(); try (final InputStream fileInputStream = new FileInputStream(tempFile)) { loader.load(fileInputStream,chosenRobot); MakelangeloRobotPanel panel = chosenRobot.getControlPanel(); if(panel!=null) panel.updateButtonAccess(); } catch(IOException e) { e.printStackTrace(); } tempFile.delete(); pm.setProgress(100); return null; } @Override public void done() { if(pm!=null) pm.close(); //System.out.println("swingWorker ended"); swingWorker=null; } }; swingWorker.addPropertyChangeListener(new PropertyChangeListener() { // Invoked when task's progress property changes. public void propertyChange(PropertyChangeEvent evt) { if (Objects.equals("progress", evt.getPropertyName())) { int progress = (Integer) evt.getNewValue(); pm.setProgress(progress); String message = String.format("%d%%.\n", progress); pm.setNote(message); if (swingWorker.isDone()) { Log.message(Translator.get("Finished")); } else if (swingWorker.isCancelled() || pm.isCanceled()) { if (pm.isCanceled()) { swingWorker.cancel(true); } Log.message(Translator.get("Cancelled")); } } } }); swingWorker.execute(); } private void setPreferredDrawStyle(int style) { prefs.putInt("Draw Style", style); } private int getPreferredDrawStyle() { return prefs.getInt("Draw Style", 0); } public boolean canSave(String filename) { return false; } public boolean save(OutputStream outputStream,MakelangeloRobot robot) { return false; } @Override public boolean canLoad() { return true; } @Override public boolean canSave() { return false; } }