001/*****************************************************************************
002 * Copyright by The HDF Group.                                               *
003 * Copyright by the Board of Trustees of the University of Illinois.         *
004 * All rights reserved.                                                      *
005 *                                                                           *
006 * This file is part of the HDF Java Products distribution.                  *
007 * The full copyright notice, including terms governing use, modification,   *
008 * and redistribution, is contained in the files COPYING and Copyright.html. *
009 * COPYING can be found at the root of the source code distribution tree.    *
010 * Or, see http://hdfgroup.org/products/hdf-java/doc/Copyright.html.         *
011 * If you do not have access to either file, you may request a copy from     *
012 * help@hdfgroup.org.                                                        *
013 ****************************************************************************/
014
015package hdf.view;
016
017import java.awt.BorderLayout;
018import java.awt.Color;
019import java.awt.Dimension;
020import java.awt.Graphics;
021import java.awt.GridLayout;
022import java.awt.Image;
023import java.awt.Point;
024import java.awt.Toolkit;
025import java.awt.Window;
026import java.awt.event.ActionEvent;
027import java.awt.event.ActionListener;
028import java.awt.event.ItemEvent;
029import java.awt.event.ItemListener;
030import java.awt.event.KeyEvent;
031import java.awt.event.MouseEvent;
032import java.awt.event.MouseListener;
033import java.awt.event.MouseMotionListener;
034import java.awt.image.IndexColorModel;
035import java.awt.image.MemoryImageSource;
036import java.util.Vector;
037
038import javax.swing.BorderFactory;
039import javax.swing.ButtonGroup;
040import javax.swing.CellEditor;
041import javax.swing.JButton;
042import javax.swing.JComboBox;
043import javax.swing.JComponent;
044import javax.swing.JDialog;
045import javax.swing.JFrame;
046import javax.swing.JOptionPane;
047import javax.swing.JPanel;
048import javax.swing.JRadioButton;
049import javax.swing.JScrollPane;
050import javax.swing.JTable;
051import javax.swing.ListSelectionModel;
052import javax.swing.WindowConstants;
053import javax.swing.border.LineBorder;
054import javax.swing.event.ChangeEvent;
055import javax.swing.table.DefaultTableCellRenderer;
056import javax.swing.table.DefaultTableModel;
057
058import hdf.object.FileFormat;
059import hdf.object.HObject;
060import hdf.object.ScalarDS;
061
062/**
063 * To view and change palette.
064 * 
065 * @author Peter X. Cao
066 * @version 2.4 9/6/2007
067 */
068public class DefaultPaletteView extends JDialog implements PaletteView,
069        MouseListener, MouseMotionListener, ActionListener, ItemListener {
070    private static final long serialVersionUID = -5092012421988388661L;
071    private final Color[] lineColors = { Color.red, Color.green, Color.blue };
072    private final String lineLabels[] = { "Red", "Green", "Blue" };
073
074    private static String PALETTE_GRAY = "Gray";
075    private static String PALETTE_DEFAULT = "Default";
076    private static String PALETTE_REVERSE_GRAY = "Reverse Gray";
077    private static String PALETTE_GRAY_WAVE = "GrayWave";
078    private static String PALETTE_RAINBOW = "Rainbow";
079    private static String PALETTE_NATURE = "Nature";
080    private static String PALETTE_WAVE = "Wave";
081
082    private JRadioButton checkRed, checkGreen, checkBlue;
083    /** Panel that draws plot of data values. */
084    private ChartPanel chartP;
085    private int x0, y0; // starting point of mouse drag
086    private Image originalImage, currentImage;
087    boolean isPaletteChanged = false;
088    byte[][] palette;
089    private ScalarDS dataset;
090    private ImageView imageView;
091    private int[][] paletteData;
092    @SuppressWarnings("rawtypes")
093    private JComboBox choicePalette;
094    private PaletteValueTable paletteValueTable;
095    private int  numberOfPalettes;
096    private boolean startEditing = false;
097
098    public DefaultPaletteView(ImageView theImageView) {
099        this(null, theImageView);
100    }
101
102    @SuppressWarnings({ "rawtypes", "unchecked" })
103    public DefaultPaletteView(ViewManager theViewer, ImageView theImageView) {
104        super((JFrame) theViewer, true);
105        setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
106        imageView = theImageView;
107        dataset = (ScalarDS) imageView.getDataObject();
108        
109        numberOfPalettes = 1;
110        choicePalette = new JComboBox();
111        choicePalette.addItemListener(this);
112
113        boolean isH5 = dataset.getFileFormat().isThisType(
114                FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5));
115
116        choicePalette.addItem("Select palette");
117        String paletteName = ((ScalarDS) dataset).getPaletteName(0);
118        
119        if (paletteName!= null)
120            paletteName = paletteName.trim();
121        
122        if (paletteName!= null && paletteName.length()>0)
123            choicePalette.addItem(paletteName);
124        
125        if (isH5 && (dataset instanceof ScalarDS)) {
126            byte[] palRefs = ((ScalarDS) dataset).getPaletteRefs();
127            if ((palRefs != null) && (palRefs.length > 8)) {
128              numberOfPalettes = palRefs.length / 8;
129            }
130        }
131        for (int i = 1; i < numberOfPalettes; i++) {
132            paletteName = ((ScalarDS) dataset).getPaletteName(i);
133            choicePalette.addItem(paletteName);
134        }
135        choicePalette.addItem(PALETTE_GRAY);
136        choicePalette.addItem(PALETTE_GRAY_WAVE);
137        choicePalette.addItem(PALETTE_RAINBOW);
138        choicePalette.addItem(PALETTE_NATURE);
139        choicePalette.addItem(PALETTE_WAVE);
140        Vector<?> plist = ViewProperties.getPaletteList();
141        int n = plist.size();
142        for (int i = 0; i < n; i++)
143            choicePalette.addItem((String) plist.get(i));
144
145        chartP = new ChartPanel();
146        chartP.setBackground(Color.white);
147
148        paletteData = new int[3][256];
149        byte[][] imagePalette = imageView.getPalette();
150        this.setTitle("Image Palette for - " + dataset.getPath()
151                + dataset.getName());
152
153        int d = 0;
154        for (int i = 0; i < 3; i++) {
155            for (int j = 0; j < 256; j++) {
156                d = imagePalette[i][j];
157                if (d < 0) {
158                    d += 256;
159                }
160                paletteData[i][j] = d;
161            }
162        }
163
164        imageView = theImageView;
165        chartP.addMouseListener(this);
166        chartP.addMouseMotionListener(this);
167
168        x0 = y0 = 0;
169        originalImage = currentImage = imageView.getImage();
170        palette = new byte[3][256];
171
172        createUI();
173        setVisible(true);
174    }
175
176    /** returns the data object displayed in this data viewer */
177    public HObject getDataObject() {
178        return dataset;
179    }
180
181    /**
182     * Creates and layouts GUI componentes.
183     */
184    private void createUI() {
185        Window owner = getOwner();
186
187        JPanel contentPane = (JPanel) getContentPane();
188        contentPane.setLayout(new BorderLayout(5, 5));
189        contentPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
190        int w = 700 + (ViewProperties.getFontSize() - 12) * 15;
191        int h = 500 + (ViewProperties.getFontSize() - 12) * 10;
192        contentPane.setPreferredSize(new Dimension(w, h));
193
194        contentPane.add(chartP, BorderLayout.CENTER);
195
196        JButton button = new JButton("  Ok  ");
197        button.addActionListener(this);
198        button.setActionCommand("Ok");
199        JPanel buttonP = new JPanel();
200        buttonP.setBorder(new LineBorder(Color.GRAY));
201        buttonP.add(button);
202        button = new JButton("Cancel");
203        button.addActionListener(this);
204        button.setActionCommand("Cancel");
205        buttonP.add(button);
206        button = new JButton("Preview");
207        button.addActionListener(this);
208        button.setActionCommand("Preview");
209        buttonP.add(button);
210
211        JPanel bottomP = new JPanel();
212        bottomP.setLayout(new BorderLayout(20, 2));
213        bottomP.add(buttonP, BorderLayout.EAST);
214
215        checkRed = new JRadioButton("Red");
216        checkRed.setForeground(Color.red);
217        checkGreen = new JRadioButton("Green");
218        checkGreen.setForeground(Color.green);
219        checkBlue = new JRadioButton("Blue");
220        checkBlue.setForeground(Color.blue);
221        checkRed.setSelected(true);
222        ButtonGroup bgroup = new ButtonGroup();
223        bgroup.add(checkRed);
224        bgroup.add(checkGreen);
225        bgroup.add(checkBlue);
226        JPanel checkP = new JPanel();
227        checkP.setBorder(new LineBorder(Color.GRAY));
228        checkP.add(checkRed);
229        checkP.add(checkGreen);
230        checkP.add(checkBlue);
231        bottomP.add(checkP, BorderLayout.WEST);
232
233        JPanel valueP = new JPanel();
234        valueP.setLayout(new GridLayout(1, 2));
235        valueP.setBorder(new LineBorder(Color.GRAY));
236        JButton valueButton = new JButton("Show Values");
237        valueButton.setActionCommand("Show palette values");
238        valueButton.addActionListener(this);
239        valueP.add(choicePalette);
240        valueP.add(valueButton);
241        bottomP.add(valueP, BorderLayout.CENTER);
242
243        contentPane.add(bottomP, BorderLayout.SOUTH);
244
245        Point l = owner.getLocation();
246        l.x += 350;
247        l.y += 200;
248        setLocation(l);
249        pack();
250    }
251
252    public void actionPerformed(ActionEvent e) {
253        String cmd = e.getActionCommand();
254
255        if (cmd.equals("Ok")) {
256            if (isPaletteChanged) {
257                this.updatePalette();
258                isPaletteChanged = false;
259                imageView.setPalette(palette);
260                imageView.setImage(currentImage);
261            }
262            super.dispose();
263        }
264        else if (cmd.equals("Cancel")) {
265            imageView.setImage(originalImage);
266            super.dispose();
267        }
268        else if (cmd.equals("Preview")) {
269            this.updatePalette();
270            imageView.setImage(currentImage);
271        }
272        else if (cmd.equals("Show palette values")) {
273            if (paletteValueTable == null) {
274                paletteValueTable = new PaletteValueTable(this);
275            }
276            paletteValueTable.refresh();
277            paletteValueTable.setVisible(true);
278        }
279        else if (cmd.equals("Hide palette values")) {
280            if (paletteValueTable != null) {
281                paletteValueTable.setVisible(false);
282            }
283        }
284    }
285
286    @Override
287    public void dispose() {
288        imageView.setImage(originalImage);
289        super.dispose();
290    }
291
292    public void itemStateChanged(ItemEvent e) {
293        Object src = e.getSource();
294
295        if (!src.equals(choicePalette)) {
296            return;
297        }
298
299        int idx = choicePalette.getSelectedIndex();
300        if (idx <= 0) {
301            return;
302        }
303
304        byte[][] imagePalette = null;
305        Object item = choicePalette.getSelectedItem();
306
307        if (item.equals(PALETTE_DEFAULT)) {
308            imagePalette = dataset.getPalette();
309        }
310        else if (item.equals(PALETTE_GRAY)) {
311            imagePalette = Tools.createGrayPalette();
312        }
313        else if (item.equals(PALETTE_REVERSE_GRAY)) {
314            imagePalette = Tools.createReverseGrayPalette();
315        }
316        else if (item.equals(PALETTE_GRAY_WAVE)) {
317            imagePalette = Tools.createGrayWavePalette();
318        }
319        else if (item.equals(PALETTE_RAINBOW)) {
320            imagePalette = Tools.createRainbowPalette();
321        }
322        else if (item.equals(PALETTE_NATURE)) {
323            imagePalette = Tools.createNaturePalette();
324        }
325        else if (item.equals(PALETTE_WAVE)) {
326            imagePalette = Tools.createWavePalette();
327        }
328        else if (idx > 0 && idx <= numberOfPalettes) {
329            imagePalette = ((ScalarDS) dataset).readPalette(idx - 1);
330        }
331        else {
332            imagePalette = Tools.readPalette((String)item);
333        }
334
335        if (imagePalette == null) {
336            return;
337        }
338
339        int d = 0;
340        for (int i = 0; i < 3; i++) {
341            for (int j = 0; j < 256; j++) {
342                d = imagePalette[i][j];
343                if (d < 0) {
344                    d += 256;
345                }
346                paletteData[i][j] = d;
347            }
348        }
349
350        chartP.repaint();
351        isPaletteChanged = true;
352
353    }
354
355    private void updatePalette() {
356        for (int i = 0; i < 256; i++) {
357            palette[0][i] = (byte) paletteData[0][i];
358            palette[1][i] = (byte) paletteData[1][i];
359            palette[2][i] = (byte) paletteData[2][i];
360        }
361
362        IndexColorModel colorModel = new IndexColorModel(8, // bits - the number
363                                                            // of bits each
364                                                            // pixel occupies
365                256, // size - the size of the color component arrays
366                palette[0], // r - the array of red color components
367                palette[1], // g - the array of green color components
368                palette[2]); // b - the array of blue color components
369
370        int w = dataset.getWidth();
371        int h = dataset.getHeight();
372        MemoryImageSource memoryImageSource = null;
373
374        try {
375            memoryImageSource = (MemoryImageSource) originalImage.getSource();
376        }
377        catch (Throwable err) {
378            memoryImageSource = null;
379        }
380
381        if (memoryImageSource == null) {
382            memoryImageSource = new MemoryImageSource(w, h, colorModel,
383                    imageView.getImageByteData(), 0, w);
384        }
385        else {
386            memoryImageSource.newPixels(imageView.getImageByteData(),
387                    colorModel, 0, w);
388        }
389
390        currentImage = Toolkit.getDefaultToolkit().createImage(
391                memoryImageSource);
392    }
393
394    public void mouseClicked(MouseEvent e) {
395    } // MouseListener
396
397    public void mouseReleased(MouseEvent e) {
398        if ((paletteValueTable != null) && paletteValueTable.isVisible()) {
399            paletteValueTable.refresh();
400        }
401    } // MouseListener
402
403    public void mouseEntered(MouseEvent e) {
404    } // MouseListener
405
406    public void mouseExited(MouseEvent e) {
407    } // MouseListener
408
409    public void mouseMoved(MouseEvent e) {
410    } // MouseMotionListener
411
412    // implementing MouseListener
413    public void mousePressed(MouseEvent e) {
414        // x0 = e.getX()-40; // takes the horizontal gap
415        // if (x0 < 0) x0 = 0;
416        // y0 = e.getY()+20;
417    }
418
419    // implementing MouseMotionListener
420    public void mouseDragged(MouseEvent e) {
421        int x1 = e.getX() - 40;// takes the vertical gap
422        if (x1 < 0) {
423            x1 = 0;
424        }
425        int y1 = e.getY() + 20;
426
427        Dimension d = chartP.getSize();
428        double ry = 255 / (double) d.height;
429        double rx = 255 / (double) d.width;
430
431        int lineIdx = 0;
432        if (checkGreen.isSelected()) {
433            lineIdx = 1;
434        }
435        else if (checkBlue.isSelected()) {
436            lineIdx = 2;
437        }
438
439        int idx = 0;
440        double b = (double) (y1 - y0) / (double) (x1 - x0);
441        double a = y0 - b * x0;
442        double value = y0 * ry;
443        int i0 = Math.min(x0, x1);
444        int i1 = Math.max(x0, x1);
445        for (int i = i0; i < i1; i++) {
446            idx = (int) (rx * i);
447            if (idx > 255) {
448                continue;
449            }
450            value = 255 - (a + b * i) * ry;
451            if (value < 0) {
452                value = 0;
453            }
454            else if (value > 255) {
455                value = 255;
456            }
457            paletteData[lineIdx][idx] = (int) value;
458        }
459
460        chartP.repaint();
461        isPaletteChanged = true;
462    }
463
464    /** The dialog to show the palette values in spreadsheet. */
465    private final class PaletteValueTable extends JDialog {
466        private static final long serialVersionUID = 6105012612969555535L;
467        private JTable valueTable;
468        private DefaultTableModel valueTableModel;
469        String rgbName = "Color";
470        String idxName = "Index";
471        int editingRow =-1, editingCol=-1;
472
473        public PaletteValueTable(DefaultPaletteView owner) {
474            super(owner);
475            String[] columnNames = { idxName, "Red", "Green", "Blue", rgbName };
476            valueTableModel = new DefaultTableModel(columnNames, 256);
477
478            valueTable = new JTable(valueTableModel) {
479                private static final long serialVersionUID = -2823793138915014637L;
480
481                @Override
482                public boolean isCellEditable(int row, int col) {
483                    return (col > 0 && col < 4);
484                }
485
486                @Override
487                public Object getValueAt(int row, int col) {
488                    if (startEditing && row==editingRow && col==editingCol)
489                        return "";
490
491                    if (col == 0)
492                        return String.valueOf(row);
493                    else if (col < 4) {
494                        return String.valueOf(paletteData[col - 1][row]);
495                    }
496                    else {
497                        return "";
498                    }
499                }
500
501                @Override
502                public boolean editCellAt(int row, int column, java.util.EventObject e) 
503                {
504                    if (!isCellEditable(row, column)) {
505                        return super.editCellAt(row, column, e);
506                    }
507
508                    if (e instanceof KeyEvent) {
509                        KeyEvent ke = (KeyEvent) e;
510                        if (ke.getID() == KeyEvent.KEY_PRESSED) {
511                            startEditing = true;
512                            editingRow = row;
513                            editingCol = column;
514                        }
515                    }
516
517                    return super.editCellAt(row, column, e);
518                }
519
520                @Override
521                public void editingStopped(ChangeEvent e) {
522                    int row = getEditingRow();
523                    int col = getEditingColumn();
524
525                    if (!isCellEditable(row, col)) {
526                        return;
527                    }
528
529                    String oldValue = (String) getValueAt(row, col);
530                    super.editingStopped(e);
531                    startEditing = false;
532                    editingRow = -1;
533                    editingCol = -1;
534
535                    Object source = e.getSource();
536
537                    if (source instanceof CellEditor) {
538                        CellEditor editor = (CellEditor) source;
539                        String newValue = (String) editor.getCellEditorValue();
540                        setValueAt(oldValue, row, col); // set back to what it
541                                                        // is
542                        updatePaletteValue(newValue, row, col - 1);
543                    }
544                }
545            };
546
547            valueTable.setName("PaletteValue");
548            valueTable.getColumn(rgbName).setCellRenderer(
549                    new DefaultTableCellRenderer() {
550                        private static final long serialVersionUID = 8390954944015521331L;
551                        Color color = Color.white;
552
553                        @Override
554                        public java.awt.Component getTableCellRendererComponent(
555                                JTable table, Object value, boolean isSelected,
556                                boolean hasFocus, int row, int col) {
557                            java.awt.Component comp = super
558                                    .getTableCellRendererComponent(table,
559                                            value, isSelected, hasFocus, row,
560                                            col);
561                            color = new Color(paletteData[0][row],
562                                    paletteData[1][row], paletteData[2][row]);
563                            comp.setBackground(color);
564                            return comp;
565                        }
566                    });
567
568            valueTable.getColumn(idxName).setCellRenderer(
569                    new DefaultTableCellRenderer() {
570                        private static final long serialVersionUID = 2786027382023940417L;
571
572                        @Override
573                        public java.awt.Component getTableCellRendererComponent(
574                                JTable table, Object value, boolean isSelected,
575                                boolean hasFocus, int row, int col) {
576                            java.awt.Component comp = super
577                                    .getTableCellRendererComponent(table,
578                                            value, isSelected, hasFocus, row,
579                                            col);
580                            comp.setBackground(Color.LIGHT_GRAY);
581                            return comp;
582                        }
583                    });
584
585            valueTable.setRowSelectionAllowed(false);
586            valueTable.setCellSelectionEnabled(true);
587            valueTable.getTableHeader().setReorderingAllowed(false);
588            valueTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
589
590            // set cell height for large fonts
591            int cellRowHeight = Math.max(16, valueTable.getFontMetrics(
592                    valueTable.getFont()).getHeight());
593            valueTable.setRowHeight(cellRowHeight);
594
595            JScrollPane scroller = new JScrollPane(valueTable);
596
597            JPanel contentPane = (JPanel) getContentPane();
598            int w = 300 + (ViewProperties.getFontSize() - 12) * 10;
599            int h = 600 + (ViewProperties.getFontSize() - 12) * 15;
600            contentPane.setPreferredSize(new Dimension(w, h));
601            contentPane.setLayout(new BorderLayout(5, 5));
602            contentPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
603            contentPane.add(scroller, BorderLayout.CENTER);
604
605            JButton button = new JButton("  Ok  ");
606            button.addActionListener(owner);
607            button.setActionCommand("Hide palette values");
608
609            JPanel tmpP = new JPanel();
610            tmpP.add(button);
611            contentPane.add(tmpP, BorderLayout.SOUTH);
612
613            Point l = owner.getLocation();
614            l.x += 100;
615            l.y += 100;
616            setLocation(l);
617            pack();
618        }
619
620        private void updatePaletteValue(String strValue, int row, int col) {
621            if (strValue == null) {
622                return;
623            }
624
625            int value = 0;
626
627            try {
628                value = Integer.parseInt(strValue);
629            }
630            catch (Exception ex) {
631                return;
632            }
633
634            if (value < 0 || value > 255) {
635                JOptionPane.showMessageDialog(this,
636                        "Value is out of range [0, 255]\n", getTitle(),
637                        JOptionPane.ERROR_MESSAGE);
638                return;
639            }
640
641            paletteData[col][row] = value;
642            chartP.repaint();
643            isPaletteChanged = true;
644        }
645
646        public void refresh() {
647            valueTable.editingStopped(new ChangeEvent(valueTable));
648            valueTable.updateUI();
649        }
650    }
651
652    /** The canvas that paints the data lines. */
653    private final class ChartPanel extends JComponent {
654        private static final long serialVersionUID = -6861041412971944L;
655
656        /**
657         * Paints the plot components.
658         */
659        @Override
660        public void paint(Graphics g) {
661            Dimension d = getSize();
662            int gap = 20;
663            int legendSpace = 60;
664            int h = d.height - gap;
665            int w = d.width - 3 * gap - legendSpace;
666
667            // draw the X axis
668            g.drawLine(2 * gap, h, w + 2 * gap, h);
669
670            // draw the Y axis
671            g.drawLine(2 * gap, h, 2 * gap, 0);
672
673            // draw X and Y labels: 10 labels for x and y
674            int dh = h / 10;
675            int dw = w / 10;
676            int dx = 25;
677            double dy = 25;
678            int xp = 2 * gap, yp = 0, x = 0, x0, y0, x1, y1;
679            double y = 0;
680
681            // draw X and Y grid labels
682            g.drawString(String.valueOf((int) y), 0, h + 8);
683            g.drawString(String.valueOf(x), xp - 5, h + gap);
684            for (int i = 0; i < 10; i++) {
685                xp += dw;
686                yp += dh;
687                x += dx;
688                y += dy;
689                g.drawLine(xp, h, xp, h - 5);
690                g.drawLine(2 * gap, h - yp, 2 * gap + 5, h - yp);
691                g.drawString(String.valueOf((int) y), 0, h - yp + 8);
692                g.drawString(String.valueOf(x), xp - 5, h + gap);
693            }
694
695            Color c = g.getColor();
696            for (int i = 0; i < 3; i++) {
697                g.setColor(lineColors[i]);
698
699                // set up the line data for drawing one line a time
700                for (int j = 0; j < 255; j++) {
701                    x0 = (w * j / 255) + 2 * gap;
702                    y0 = (h - h * paletteData[i][j] / 255);
703                    x1 = (w * (j + 1) / 255) + 2 * gap;
704                    y1 = (h - h * (paletteData[i][j + 1]) / 255);
705                    g.drawLine(x0, y0, x1, y1);
706                }
707
708                x0 = w + legendSpace;
709                y0 = gap + gap * i;
710                g.drawLine(x0, y0, x0 + 7, y0);
711                g.drawString(lineLabels[i], x0 + 10, y0 + 3);
712            }
713
714            g.setColor(c); // set the color back to its default
715
716            // draw a box on the legend
717            g.drawRect(w + legendSpace - 10, 10, legendSpace, 10 * gap);
718        } // public void paint(Graphics g)
719
720    } // private class ChartPanel extends Canvas
721
722} // private class PaletteView extends ChartView
723