001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.gui.sequence.tools;
020
021import icy.gui.component.IcyTextField;
022import icy.gui.component.sequence.SequencePreviewPanel;
023import icy.image.IcyBufferedImage;
024import icy.image.IcyBufferedImageUtil;
025import icy.image.IcyBufferedImageUtil.FilterType;
026import icy.math.UnitUtil;
027import icy.resource.ResourceUtil;
028import icy.sequence.AbstractSequenceModel;
029import icy.sequence.Sequence;
030
031import java.awt.BorderLayout;
032import java.awt.Color;
033import java.awt.Component;
034import java.awt.GridBagConstraints;
035import java.awt.GridBagLayout;
036import java.awt.Insets;
037import java.awt.event.ActionEvent;
038import java.awt.event.ActionListener;
039import java.awt.image.BufferedImage;
040
041import javax.swing.Box;
042import javax.swing.BoxLayout;
043import javax.swing.DefaultComboBoxModel;
044import javax.swing.JCheckBox;
045import javax.swing.JComboBox;
046import javax.swing.JLabel;
047import javax.swing.JPanel;
048import javax.swing.JSpinner;
049import javax.swing.JSpinner.DefaultEditor;
050import javax.swing.SpinnerNumberModel;
051import javax.swing.SwingConstants;
052import javax.swing.UIManager;
053import javax.swing.border.TitledBorder;
054import javax.swing.event.ChangeEvent;
055import javax.swing.event.ChangeListener;
056
057/**
058 * @author Stephane
059 */
060public abstract class SequenceBaseResizePanel extends JPanel
061{
062    /**
063     * 
064     */
065    private static final long serialVersionUID = -9220345511598410844L;
066
067    protected enum SizeUnit
068    {
069        PIXEL, PERCENT, MICRON
070    }
071
072    // pixel / micron
073
074    protected class OriginalModel extends AbstractSequenceModel
075    {
076        public OriginalModel()
077        {
078            super();
079        }
080
081        @Override
082        public int getSizeX()
083        {
084            return getMaxSizeX();
085        }
086
087        @Override
088        public int getSizeY()
089        {
090            return getMaxSizeY();
091        }
092
093        @Override
094        public int getSizeZ()
095        {
096            return sequence.getSizeZ();
097        }
098
099        @Override
100        public int getSizeT()
101        {
102            return sequence.getSizeT();
103        }
104
105        @Override
106        public int getSizeC()
107        {
108            return sequence.getSizeC();
109        }
110
111        @Override
112        public BufferedImage getImage(int t, int z)
113        {
114            return sequence.getImage(t, z);
115        }
116
117        @Override
118        public BufferedImage getImage(int t, int z, int c)
119        {
120            return sequence.getImage(t, z, c);
121        }
122    }
123
124    protected class ResultModel extends AbstractSequenceModel
125    {
126        public ResultModel()
127        {
128            super();
129        }
130
131        @Override
132        public int getSizeX()
133        {
134            return getMaxSizeX();
135        }
136
137        @Override
138        public int getSizeY()
139        {
140            return getMaxSizeY();
141        }
142
143        @Override
144        public int getSizeZ()
145        {
146            return sequence.getSizeZ();
147        }
148
149        @Override
150        public int getSizeT()
151        {
152            return sequence.getSizeT();
153        }
154
155        @Override
156        public int getSizeC()
157        {
158            return sequence.getSizeC();
159        }
160
161        @Override
162        public BufferedImage getImage(int t, int z)
163        {
164            try
165            {
166                return IcyBufferedImageUtil.scale(sequence.getImage(t, z), getNewWidth(), getNewHeight(),
167                        getResizeContent(), getXAlign(), getYAlign(), getFilterType());
168            }
169            catch (OutOfMemoryError e)
170            {
171                return null;
172            }
173        }
174
175        @Override
176        public BufferedImage getImage(int t, int z, int c)
177        {
178            return ((IcyBufferedImage) getImage(t, z)).getImage(c);
179        }
180    }
181
182    final Sequence sequence;
183
184    // GUI
185    protected JCheckBox keepRatioCheckBox;
186    protected JSpinner heightSpinner;
187    protected JSpinner widthSpinner;
188    protected SequencePreviewPanel originalPreview;
189    protected SequencePreviewPanel resultPreview;
190    protected JPanel infoPanel;
191    protected IcyTextField widthField;
192    protected IcyTextField heightField;
193    protected IcyTextField sizeField;
194    protected JComboBox sizeUnitComboBox;
195    protected JLabel accolLeftLabel;
196    protected JPanel panel;
197    protected Component horizontalGlue;
198    protected Component horizontalGlue_1;
199    protected JPanel settingPanel;
200
201    /**
202     * Create the panel.
203     */
204    public SequenceBaseResizePanel(Sequence sequence)
205    {
206        super();
207
208        this.sequence = sequence;
209
210        initialize();
211
212        setNewWidth(sequence.getSizeX());
213        setNewHeight(sequence.getSizeY());
214
215        accolLeftLabel.setIcon(ResourceUtil.getImageIcon(ResourceUtil.IMAGE_ACCOLADE_LEFT));
216        accolLeftLabel.setText(null);
217
218        originalPreview.setFitToView(false);
219        resultPreview.setFitToView(false);
220        originalPreview.setModel(new OriginalModel());
221        resultPreview.setModel(new ResultModel());
222
223        updatePreview();
224
225        final ChangeListener spinnerChangeListener = new ChangeListener()
226        {
227            @Override
228            public void stateChanged(ChangeEvent e)
229            {
230                // maintain ratio
231                if (keepRatioCheckBox.isSelected())
232                {
233                    final Sequence seq = SequenceBaseResizePanel.this.sequence;
234
235                    if (e.getSource() == widthSpinner)
236                    {
237                        // adjust height
238                        final double ratio = (double) getNewWidth() / (double) seq.getWidth();
239                        setNewHeight((int) Math.round(seq.getHeight() * ratio));
240                    }
241                    else
242                    {
243                        // adjust width
244                        final double ratio = (double) getNewHeight() / (double) seq.getHeight();
245                        setNewWidth((int) Math.round(seq.getWidth() * ratio));
246                    }
247                }
248
249                updatePreview();
250            }
251        };
252        heightSpinner.addChangeListener(spinnerChangeListener);
253        widthSpinner.addChangeListener(spinnerChangeListener);
254
255        sizeUnitComboBox.addActionListener(new ActionListener()
256        {
257            @Override
258            public void actionPerformed(ActionEvent e)
259            {
260                // convert width and height in new unit
261                final int w = Integer.parseInt(widthField.getText());
262                final int h = Integer.parseInt(heightField.getText());
263
264                setNewWidth(w);
265                setNewHeight(h);
266            }
267        });
268    }
269
270    protected void initialize()
271    {
272        setLayout(new BorderLayout(0, 0));
273
274        panel = new JPanel();
275        panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
276        add(panel, BorderLayout.NORTH);
277
278        infoPanel = new JPanel();
279        panel.add(infoPanel);
280        infoPanel.setBorder(new TitledBorder(UIManager.getBorder("TitledBorder.border"), "Size in pixel",
281                TitledBorder.LEADING, TitledBorder.TOP, null, new Color(0, 0, 0)));
282        GridBagLayout gbl_infoPanel = new GridBagLayout();
283        gbl_infoPanel.columnWidths = new int[] {20, 100, 20, 100, 20, 100, 20, 0};
284        gbl_infoPanel.rowHeights = new int[] {0, 0, 0};
285        gbl_infoPanel.columnWeights = new double[] {1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, Double.MIN_VALUE};
286        gbl_infoPanel.rowWeights = new double[] {0.0, 0.0, Double.MIN_VALUE};
287        infoPanel.setLayout(gbl_infoPanel);
288
289        final JLabel lblOriginalWidth = new JLabel("Width");
290        GridBagConstraints gbc_lblOriginalWidth = new GridBagConstraints();
291        gbc_lblOriginalWidth.fill = GridBagConstraints.BOTH;
292        gbc_lblOriginalWidth.insets = new Insets(0, 0, 5, 5);
293        gbc_lblOriginalWidth.gridx = 1;
294        gbc_lblOriginalWidth.gridy = 0;
295        infoPanel.add(lblOriginalWidth, gbc_lblOriginalWidth);
296        lblOriginalWidth.setToolTipText("");
297
298        final JLabel lblNewLabel_3 = new JLabel("Height");
299        GridBagConstraints gbc_lblNewLabel_3 = new GridBagConstraints();
300        gbc_lblNewLabel_3.fill = GridBagConstraints.BOTH;
301        gbc_lblNewLabel_3.insets = new Insets(0, 0, 5, 5);
302        gbc_lblNewLabel_3.gridx = 3;
303        gbc_lblNewLabel_3.gridy = 0;
304        infoPanel.add(lblNewLabel_3, gbc_lblNewLabel_3);
305        lblNewLabel_3.setToolTipText("");
306
307        final JLabel lblNewLabel_2 = new JLabel("Memory size");
308        GridBagConstraints gbc_lblNewLabel_2 = new GridBagConstraints();
309        gbc_lblNewLabel_2.fill = GridBagConstraints.BOTH;
310        gbc_lblNewLabel_2.insets = new Insets(0, 0, 5, 5);
311        gbc_lblNewLabel_2.gridx = 5;
312        gbc_lblNewLabel_2.gridy = 0;
313        infoPanel.add(lblNewLabel_2, gbc_lblNewLabel_2);
314        lblNewLabel_2.setToolTipText("");
315
316        widthField = new IcyTextField();
317        widthField.setToolTipText("Width in pixel");
318        widthField.setText("0000");
319        widthField.setEditable(false);
320        GridBagConstraints gbc_widthField = new GridBagConstraints();
321        gbc_widthField.fill = GridBagConstraints.BOTH;
322        gbc_widthField.insets = new Insets(0, 0, 0, 5);
323        gbc_widthField.gridx = 1;
324        gbc_widthField.gridy = 1;
325        infoPanel.add(widthField, gbc_widthField);
326        widthField.setColumns(5);
327
328        heightField = new IcyTextField();
329        heightField.setToolTipText("Height in pixel");
330        heightField.setText("0");
331        heightField.setEditable(false);
332        GridBagConstraints gbc_heightField = new GridBagConstraints();
333        gbc_heightField.fill = GridBagConstraints.BOTH;
334        gbc_heightField.insets = new Insets(0, 0, 0, 5);
335        gbc_heightField.gridx = 3;
336        gbc_heightField.gridy = 1;
337        infoPanel.add(heightField, gbc_heightField);
338        heightField.setColumns(5);
339
340        sizeField = new IcyTextField();
341        sizeField.setToolTipText("Memory size");
342        sizeField.setText("0.0B");
343        sizeField.setEditable(false);
344        GridBagConstraints gbc_sizeField = new GridBagConstraints();
345        gbc_sizeField.insets = new Insets(0, 0, 0, 5);
346        gbc_sizeField.fill = GridBagConstraints.BOTH;
347        gbc_sizeField.gridx = 5;
348        gbc_sizeField.gridy = 1;
349        infoPanel.add(sizeField, gbc_sizeField);
350        sizeField.setColumns(5);
351
352        settingPanel = new JPanel();
353        panel.add(settingPanel);
354        settingPanel.setBorder(new TitledBorder(UIManager.getBorder("TitledBorder.border"), "Setting",
355                TitledBorder.LEADING, TitledBorder.TOP, null, new Color(0, 0, 0)));
356        GridBagLayout gbl_settingPanel = new GridBagLayout();
357        gbl_settingPanel.columnWidths = new int[] {20, 100, 20, 100, 20, 100, 20, 0};
358        gbl_settingPanel.rowHeights = new int[] {0, 0, 0, 0, 10, 0, 0, 0};
359        gbl_settingPanel.columnWeights = new double[] {1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, Double.MIN_VALUE};
360        gbl_settingPanel.rowWeights = new double[] {0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, Double.MIN_VALUE};
361        settingPanel.setLayout(gbl_settingPanel);
362
363        final JLabel lblWidth = new JLabel("Width");
364        GridBagConstraints gbc_lblWidth = new GridBagConstraints();
365        gbc_lblWidth.fill = GridBagConstraints.BOTH;
366        gbc_lblWidth.insets = new Insets(0, 0, 5, 5);
367        gbc_lblWidth.gridx = 1;
368        gbc_lblWidth.gridy = 0;
369        settingPanel.add(lblWidth, gbc_lblWidth);
370
371        widthSpinner = new JSpinner();
372        widthSpinner.setModel(new SpinnerNumberModel(new Integer(1), new Integer(1), null, new Integer(1)));
373        widthSpinner.setToolTipText("New width to set");
374        GridBagConstraints gbc_widthSpinner = new GridBagConstraints();
375        gbc_widthSpinner.fill = GridBagConstraints.BOTH;
376        gbc_widthSpinner.insets = new Insets(0, 0, 5, 5);
377        gbc_widthSpinner.gridx = 1;
378        gbc_widthSpinner.gridy = 1;
379        settingPanel.add(widthSpinner, gbc_widthSpinner);
380
381        final JLabel lblNewLabel = new JLabel("Height");
382        GridBagConstraints gbc_lblNewLabel = new GridBagConstraints();
383        gbc_lblNewLabel.fill = GridBagConstraints.BOTH;
384        gbc_lblNewLabel.insets = new Insets(0, 0, 5, 5);
385        gbc_lblNewLabel.gridx = 1;
386        gbc_lblNewLabel.gridy = 2;
387        settingPanel.add(lblNewLabel, gbc_lblNewLabel);
388
389        accolLeftLabel = new JLabel("");
390        accolLeftLabel.setHorizontalAlignment(SwingConstants.CENTER);
391        GridBagConstraints gbc_accolLeftLabel = new GridBagConstraints();
392        gbc_accolLeftLabel.fill = GridBagConstraints.BOTH;
393        gbc_accolLeftLabel.gridheight = 3;
394        gbc_accolLeftLabel.insets = new Insets(0, 0, 5, 5);
395        gbc_accolLeftLabel.gridx = 2;
396        gbc_accolLeftLabel.gridy = 1;
397        settingPanel.add(accolLeftLabel, gbc_accolLeftLabel);
398        lblNewLabel.setLabelFor(heightSpinner);
399
400        sizeUnitComboBox = new JComboBox();
401        sizeUnitComboBox.setMaximumRowCount(3);
402        sizeUnitComboBox.setToolTipText("Width / Height unit");
403        sizeUnitComboBox.setModel(new DefaultComboBoxModel(new String[] {"pixel", "%", "\u00B5m"}));
404        sizeUnitComboBox.setSelectedIndex(0);
405        GridBagConstraints gbc_sizeUnitComboBox = new GridBagConstraints();
406        gbc_sizeUnitComboBox.fill = GridBagConstraints.HORIZONTAL;
407        gbc_sizeUnitComboBox.gridheight = 3;
408        gbc_sizeUnitComboBox.insets = new Insets(0, 0, 5, 5);
409        gbc_sizeUnitComboBox.gridx = 3;
410        gbc_sizeUnitComboBox.gridy = 1;
411        settingPanel.add(sizeUnitComboBox, gbc_sizeUnitComboBox);
412
413        heightSpinner = new JSpinner();
414        heightSpinner.setModel(new SpinnerNumberModel(new Integer(1), new Integer(1), null, new Integer(1)));
415        heightSpinner.setToolTipText("New height to set");
416        GridBagConstraints gbc_heightSpinner = new GridBagConstraints();
417        gbc_heightSpinner.fill = GridBagConstraints.BOTH;
418        gbc_heightSpinner.insets = new Insets(0, 0, 5, 5);
419        gbc_heightSpinner.gridx = 1;
420        gbc_heightSpinner.gridy = 3;
421        settingPanel.add(heightSpinner, gbc_heightSpinner);
422
423        keepRatioCheckBox = new JCheckBox("Keep ratio");
424        keepRatioCheckBox.setVerticalAlignment(SwingConstants.TOP);
425        keepRatioCheckBox.setToolTipText("Keep original aspect ratio");
426        keepRatioCheckBox.setSelected(true);
427        GridBagConstraints gbc_keepRatioCheckBox = new GridBagConstraints();
428        gbc_keepRatioCheckBox.gridwidth = 3;
429        gbc_keepRatioCheckBox.fill = GridBagConstraints.BOTH;
430        gbc_keepRatioCheckBox.insets = new Insets(0, 0, 5, 5);
431        gbc_keepRatioCheckBox.gridx = 1;
432        gbc_keepRatioCheckBox.gridy = 5;
433        settingPanel.add(keepRatioCheckBox, gbc_keepRatioCheckBox);
434
435        horizontalGlue = Box.createHorizontalGlue();
436        GridBagConstraints gbc_horizontalGlue = new GridBagConstraints();
437        gbc_horizontalGlue.fill = GridBagConstraints.HORIZONTAL;
438        gbc_horizontalGlue.insets = new Insets(0, 0, 0, 5);
439        gbc_horizontalGlue.gridx = 5;
440        gbc_horizontalGlue.gridy = 6;
441        settingPanel.add(horizontalGlue, gbc_horizontalGlue);
442
443        horizontalGlue_1 = Box.createHorizontalGlue();
444        GridBagConstraints gbc_horizontalGlue_1 = new GridBagConstraints();
445        gbc_horizontalGlue_1.fill = GridBagConstraints.HORIZONTAL;
446        gbc_horizontalGlue_1.gridx = 6;
447        gbc_horizontalGlue_1.gridy = 6;
448        settingPanel.add(horizontalGlue_1, gbc_horizontalGlue_1);
449
450        final JPanel previewPanel = new JPanel();
451        previewPanel.setBorder(new TitledBorder(null, "Preview", TitledBorder.LEADING, TitledBorder.TOP, null, null));
452        add(previewPanel, BorderLayout.CENTER);
453        previewPanel.setLayout(new BoxLayout(previewPanel, BoxLayout.LINE_AXIS));
454
455        originalPreview = new SequencePreviewPanel("Original");
456        previewPanel.add(originalPreview);
457
458        resultPreview = new SequencePreviewPanel("Result");
459        previewPanel.add(resultPreview);
460    }
461
462    void updatePreview()
463    {
464        final int w = getNewWidth();
465        final int h = getNewHeight();
466
467        widthField.setText(Integer.toString(w));
468        heightField.setText(Integer.toString(h));
469        sizeField.setText(UnitUtil.getBytesString(w * h * sequence.getSizeC() * sequence.getSizeZ()
470                * sequence.getSizeT() * sequence.getDataType_().getSize()));
471
472        originalPreview.imageChanged();
473        resultPreview.imageChanged();
474    }
475
476    public Sequence getSequence()
477    {
478        return sequence;
479    }
480
481    /**
482     * pixel resolution X (micron / pixel)
483     */
484    public double getPixelSizeX()
485    {
486        if (sequence != null)
487            return sequence.getPixelSizeX();
488
489        return 1d;
490    }
491
492    /**
493     * pixel resolution X (micron / pixel)
494     */
495    public double getPixelSizeY()
496    {
497        if (sequence != null)
498            return sequence.getPixelSizeY();
499
500        return 1d;
501    }
502
503    public SizeUnit getSizeUnit()
504    {
505        switch (sizeUnitComboBox.getSelectedIndex())
506        {
507            default:
508            case 0:
509                return SizeUnit.PIXEL;
510            case 1:
511                return SizeUnit.PERCENT;
512            case 2:
513                return SizeUnit.MICRON;
514        }
515    }
516
517    public int unitToPixel(double value, int originPixel, SizeUnit unit, double micronPerPixel)
518    {
519        switch (unit)
520        {
521            default:
522            case PIXEL:
523                return (int) Math.round(value);
524            case PERCENT:
525                return (int) Math.round((originPixel * value) / 100d);
526            case MICRON:
527                return (int) Math.round(value / micronPerPixel);
528        }
529    }
530
531    public int unitToPixelX(double value, int originPixel, SizeUnit unit)
532    {
533        return unitToPixel(value, originPixel, unit, getPixelSizeX());
534    }
535
536    public int unitToPixelY(double value, int originPixel, SizeUnit unit)
537    {
538        return unitToPixel(value, originPixel, unit, getPixelSizeY());
539    }
540
541    public double pixelToUnit(int value, int originPixel, SizeUnit unit, double micronPerPixel)
542    {
543        switch (unit)
544        {
545            default:
546            case PIXEL:
547                return value;
548            case PERCENT:
549                return (int) Math.round((value * 100d) / originPixel);
550            case MICRON:
551                return (int) (value * micronPerPixel);
552        }
553    }
554
555    public double pixelXToUnit(int value, int originPixel, SizeUnit unit)
556    {
557        return pixelToUnit(value, originPixel, unit, getPixelSizeX());
558    }
559
560    public double pixelYToUnit(int value, int originPixel, SizeUnit unit)
561    {
562        return pixelToUnit(value, originPixel, unit, getPixelSizeY());
563    }
564
565    public double getSpinnerSizeValue(JSpinner spinner)
566    {
567        switch (getSizeUnit())
568        {
569            default:
570            case PIXEL:
571                return ((Integer) spinner.getValue()).intValue();
572
573            case PERCENT:
574            case MICRON:
575                return ((Double) spinner.getValue()).doubleValue();
576        }
577    }
578
579    public int getNewWidth()
580    {
581        final int result = unitToPixelX(getSpinnerSizeValue(widthSpinner), sequence.getSizeX(), getSizeUnit());
582
583        return Math.min(65535, Math.max(1, result));
584    }
585
586    public int getNewHeight()
587    {
588        final int result = unitToPixelY(getSpinnerSizeValue(heightSpinner), sequence.getSizeY(), getSizeUnit());
589
590        return Math.min(Math.max(1, result), 65535);
591    }
592
593    void setSpinnerSizeValue(JSpinner spinner, double value)
594    {
595        switch (getSizeUnit())
596        {
597            default:
598            case PIXEL:
599                spinner.setModel(new SpinnerNumberModel((int) value, 0, 65535, 1));
600                // we don't want the model to affect
601                ((DefaultEditor) spinner.getEditor()).getTextField().setColumns(1);
602                break;
603
604            case PERCENT:
605                spinner.setModel(new SpinnerNumberModel(value, 0d, Double.MAX_VALUE, 1d));
606                // we don't want the model to affect
607                ((DefaultEditor) spinner.getEditor()).getTextField().setColumns(1);
608                break;
609
610            case MICRON:
611                spinner.setModel(new SpinnerNumberModel(value, 0d, Double.MAX_VALUE, 0.01d));
612                // we don't want the model to affect
613                ((DefaultEditor) spinner.getEditor()).getTextField().setColumns(1);
614                break;
615        }
616    }
617
618    void setNewWidth(int value)
619    {
620        setSpinnerSizeValue(widthSpinner, pixelXToUnit(value, sequence.getSizeX(), getSizeUnit()));
621    }
622
623    void setNewHeight(int value)
624    {
625        setSpinnerSizeValue(heightSpinner, pixelYToUnit(value, sequence.getSizeY(), getSizeUnit()));
626    }
627
628    public int getMaxSizeX()
629    {
630        return Math.max(getNewWidth(), sequence.getSizeX());
631    }
632
633    public int getMaxSizeY()
634    {
635        return Math.max(getNewHeight(), sequence.getSizeY());
636    }
637
638    public abstract FilterType getFilterType();
639
640    public abstract boolean getResizeContent();
641
642    public abstract int getXAlign();
643
644    public abstract int getYAlign();
645}