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 https://support.hdfgroup.org/products/licenses.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.ImageView;
016
017import java.awt.Color;
018import java.awt.Dimension;
019import java.awt.Graphics;
020import java.awt.Graphics2D;
021import java.awt.Image;
022import java.awt.Rectangle;
023import java.awt.RenderingHints;
024import java.awt.Toolkit;
025import java.awt.image.BufferedImage;
026import java.awt.image.ColorModel;
027import java.awt.image.ComponentColorModel;
028import java.awt.image.DataBufferInt;
029import java.awt.image.DirectColorModel;
030import java.awt.image.FilteredImageSource;
031import java.awt.image.ImageFilter;
032import java.awt.image.ImageObserver;
033import java.awt.image.ImageProducer;
034import java.awt.image.IndexColorModel;
035import java.awt.image.PixelGrabber;
036import java.awt.image.RGBImageFilter;
037import java.awt.image.WritableRaster;
038
039import java.io.BufferedWriter;
040import java.io.File;
041import java.io.FileWriter;
042import java.io.PrintWriter;
043import java.io.RandomAccessFile;
044
045import java.lang.reflect.Array;
046import java.text.DecimalFormat;
047
048import java.util.ArrayList;
049import java.util.BitSet;
050import java.util.HashMap;
051import java.util.Hashtable;
052import java.util.Iterator;
053import java.util.List;
054import java.util.Vector;
055
056import org.eclipse.swt.SWT;
057import org.eclipse.swt.custom.ScrolledComposite;
058import org.eclipse.swt.events.DisposeEvent;
059import org.eclipse.swt.events.DisposeListener;
060import org.eclipse.swt.events.ModifyEvent;
061import org.eclipse.swt.events.ModifyListener;
062import org.eclipse.swt.events.MouseEvent;
063import org.eclipse.swt.events.MouseListener;
064import org.eclipse.swt.events.MouseMoveListener;
065import org.eclipse.swt.events.MouseWheelListener;
066import org.eclipse.swt.events.PaintEvent;
067import org.eclipse.swt.events.PaintListener;
068import org.eclipse.swt.events.SelectionAdapter;
069import org.eclipse.swt.events.SelectionEvent;
070import org.eclipse.swt.events.TraverseEvent;
071import org.eclipse.swt.events.TraverseListener;
072
073import org.eclipse.swt.graphics.Font;
074import org.eclipse.swt.graphics.FontData;
075import org.eclipse.swt.graphics.GC;
076import org.eclipse.swt.graphics.ImageData;
077import org.eclipse.swt.graphics.PaletteData;
078import org.eclipse.swt.graphics.Point;
079import org.eclipse.swt.graphics.RGB;
080
081import org.eclipse.swt.layout.GridData;
082import org.eclipse.swt.layout.GridLayout;
083
084import org.eclipse.swt.widgets.Button;
085import org.eclipse.swt.widgets.Canvas;
086import org.eclipse.swt.widgets.Composite;
087import org.eclipse.swt.widgets.Dialog;
088import org.eclipse.swt.widgets.Display;
089import org.eclipse.swt.widgets.Event;
090import org.eclipse.swt.widgets.FileDialog;
091import org.eclipse.swt.widgets.Label;
092import org.eclipse.swt.widgets.Listener;
093import org.eclipse.swt.widgets.Menu;
094import org.eclipse.swt.widgets.MenuItem;
095import org.eclipse.swt.widgets.Scale;
096import org.eclipse.swt.widgets.ScrollBar;
097import org.eclipse.swt.widgets.Shell;
098import org.eclipse.swt.widgets.Slider;
099import org.eclipse.swt.widgets.Text;
100import org.eclipse.swt.widgets.ToolBar;
101import org.eclipse.swt.widgets.ToolItem;
102import org.eclipse.swt.widgets.TreeItem;
103
104import hdf.object.Group;
105import hdf.object.HObject;
106import hdf.object.ScalarDS;
107import hdf.view.Chart;
108import hdf.view.DefaultFileFilter;
109import hdf.view.Tools;
110import hdf.view.ViewProperties;
111import hdf.view.ViewProperties.BITMASK_OP;
112import hdf.view.ViewProperties.DataViewType;
113import hdf.view.DataView.DataViewFactory;
114import hdf.view.DataView.DataViewFactoryProducer;
115import hdf.view.DataView.DataViewManager;
116import hdf.view.PaletteView.PaletteView;
117import hdf.view.TreeView.TreeView;
118import hdf.view.dialog.NewDatasetDialog;
119
120/**
121 * ImageView displays an HDF dataset as an image.
122 *
123 * A scalar dataset in HDF can be displayed in image or table. By default, an
124 * HDF4 GR image and HDF5 image is displayed as an image. Other scalar datasets
125 * are display in a two-dimensional table.
126 *
127 * Users can also choose to display a scalar dataset as image. Currently this version
128 * of the ImageView only supports 8-bit raster image with indexed RGB color
129 * model of 256 colors or 24-bit true color raster image. Data of other type
130 * will be converted to 8-bit integer. The simple linear conversion is used for
131 * this purpose:
132 *
133 * <pre>
134 * y = f * (x - min),
135 *       where y   = the value of 8-bit integer,
136 *             x   = the value of original data,
137 *             f   = 255/(max-min), conversion factor,
138 *             max = the maximum of the original data,
139 *             min = the minimum of the original data.
140 * </pre>
141 *
142 * A default color table is provided for images without palette attached to it.
143 * Current choice of default palettes include Gray, Rainbow, Nature and Wave.
144 * For more infomation on palette,
145 * read <a href="https://support.hdfgroup.org/HDF5/doc/ADGuide/ImageSpec.html">HDF5 Image and Palette Specification</a>
146 *
147 * @author Jordan T. Henderson
148 * @version 2.4 2//2016
149 */
150public class DefaultImageView implements ImageView
151{
152    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultImageView.class);
153
154    private final Display           display = Display.getDefault();
155    private final Shell             shell;
156    private Font                    curFont;
157
158    /** Horizontal direction to flip an image. */
159    public static final int         FLIP_HORIZONTAL = 0;
160
161    /** Vertical direction to flip an image. */
162    public static final int         FLIP_VERTICAL = 1;
163
164    /** ROTATE IMAGE 90 DEGREE CLOCKWISE. */
165    public static final int         ROTATE_CW_90 = 10;
166
167    /** ROTATE IMAGE COUNTER CLOCKWISE 90 DEGREE. */
168    public static final int         ROTATE_CCW_90 = 11;
169
170    /**
171     * The main HDFView.
172     */
173    private final DataViewManager   viewer;
174
175    private Toolkit                 toolkit;
176
177    /**
178     * The Scalar Dataset.
179     */
180    private ScalarDS                dataset;
181
182    /**
183     * The Component containing the image.
184     */
185    private ImageComponent          imageComponent;
186
187    /**
188     * The Label for the image origin.
189     */
190    private Label                   imageOriginLabel;
191
192    /**
193     * The image contained in the ImageView.
194     */
195    private Image                   image;
196
197    /**
198     * The zooming factor of this image.
199     */
200    private float                   zoomFactor;
201
202    /**
203     * The byte data array of the image.
204     */
205    private byte[]                  imageByteData;
206
207    /**
208     * The color table of the image.
209     */
210    private byte[][]                imagePalette;
211
212    /**
213     * The title of this imageview.
214     */
215    private String                  frameTitle;
216
217    /** TextField to show the image value. */
218    private Text                    valueField;
219
220    /** Flag to indicate if the image is a true color image */
221    private boolean                 isTrueColor;
222
223    /** Flag to indicate if the image is a 3D */
224    private boolean                 is3D;
225
226    /** Flag to indicate whether to show pixel values in ImageComponent */
227    private boolean                 showValues = false;
228
229    /** Flag to indicate if the image is plane interleaved */
230    private boolean                 isPlaneInterlace;
231
232    private boolean                 isHorizontalFlipped = false;
233
234    private boolean                 isVerticalFlipped = false;
235
236    private int                     rotateCount = 0;
237
238    /** the number type of the image data */
239    private char                    NT;
240
241    /** the raw data of the image */
242    private Object                  data;
243
244    private boolean                 isUnsignedConverted = false;
245
246    private double[]                dataRange;
247    private double[]                originalRange = { 0, 0 };
248
249    private PaletteComponent        paletteComponent;
250
251    private int                     animationSpeed = 2;
252
253    private List                    rotateRelatedItems;
254
255    private ScrolledComposite       imageScroller;
256
257    private Text                    frameField;
258
259    private long                    curFrame = 0;
260    private long                    maxFrame = 1;
261
262    private BufferedImage           bufferedImage;
263
264    private ContrastSlider          contrastSlider;
265
266    private int                     indexBase = 0;
267    private int[]                   dataDist = null;
268
269    /**
270     * equates to brightness
271     */
272    private boolean                 doAutoGainContrast = false;
273    private double[]                gainBias;
274    private double[]                gainBiasCurrent;
275
276    /**
277     * int array to hold unsigned short or signed int data from applying the
278     * autogain
279     */
280    private Object                  autoGainData;
281
282    private BitSet                  bitmask;
283    private boolean                 convertByteData = false;
284    private BITMASK_OP              bitmaskOP = BITMASK_OP.EXTRACT;
285
286    private enum Origin {
287        UPPER_LEFT, LOWER_LEFT, UPPER_RIGHT, LOWER_RIGHT
288    }
289
290    private Origin                  imageOrigin = null;
291
292    private List<Integer>           invalidValueIndex;
293
294    /**
295     * Constructs an ImageView.
296     *
297     * @param theView
298     *            the main HDFView.
299     */
300    public DefaultImageView(DataViewManager theView) {
301        this(theView, null);
302    }
303
304    /**
305     * Constructs an ImageView.
306     *
307     * @param theView
308     *            the main HDFView.
309     * @param map
310     *            the properties on how to show the data. The map is used to
311     *            allow applications to pass properties on how to display the
312     *            data, such as, transposing data, showing data as character,
313     *            applying bitmask, and etc. Predefined keys are listed at
314     *            ViewProperties.DATA_VIEW_KEY.
315     */
316    @SuppressWarnings("rawtypes")
317    public DefaultImageView(DataViewManager theView, HashMap map) {
318        shell = new Shell(display, SWT.SHELL_TRIM);
319
320        shell.setData(this);
321
322        shell.setImage(ViewProperties.getImageIcon());
323        shell.setLayout(new GridLayout(1, true));
324
325        shell.addDisposeListener(new DisposeListener() {
326            @Override
327            public void widgetDisposed(DisposeEvent e) {
328                // reload the data when it is displayed next time
329                // because the display type (table or image) may be
330                // different.
331                if ((dataset != null) && !dataset.isImage()) {
332                    dataset.clearData();
333                }
334
335                if (curFont != null)
336                    curFont.dispose();
337
338                data = null;
339                image = null;
340                imageByteData = null;
341                imageComponent = null;
342                autoGainData = null;
343                ((Vector) rotateRelatedItems).setSize(0);
344
345                viewer.removeDataView(DefaultImageView.this);
346
347                System.runFinalization();
348                System.gc();
349            }
350        });
351
352        try {
353            curFont = new Font(
354                    display,
355                    ViewProperties.getFontType(),
356                    ViewProperties.getFontSize(),
357                    SWT.NORMAL);
358        }
359        catch (Exception ex) {
360            curFont = null;
361        }
362
363        shell.setFont(curFont);
364
365        viewer = theView;
366        zoomFactor = 1.0f;
367        imageByteData = null;
368        imagePalette = null;
369        paletteComponent = null;
370        isTrueColor = false;
371        is3D = false;
372        isPlaneInterlace = false;
373        data = null;
374        NT = 0;
375        showValues = ViewProperties.showImageValues();
376        rotateRelatedItems = new Vector(10);
377        imageScroller = null;
378        gainBias = null;
379        gainBiasCurrent = null;
380        autoGainData = null;
381        contrastSlider = null;
382        bitmask = null;
383        invalidValueIndex = new ArrayList<>();
384
385        toolkit = Toolkit.getDefaultToolkit();
386
387        String origStr = ViewProperties.getImageOrigin();
388        if (ViewProperties.ORIGIN_LL.equalsIgnoreCase(origStr))
389            imageOrigin = Origin.LOWER_LEFT;
390        else if (ViewProperties.ORIGIN_UR.equalsIgnoreCase(origStr))
391            imageOrigin = Origin.UPPER_RIGHT;
392        else if (ViewProperties.ORIGIN_LR.equalsIgnoreCase(origStr))
393            imageOrigin = Origin.LOWER_RIGHT;
394        else
395            imageOrigin = Origin.UPPER_LEFT;
396
397        if (ViewProperties.isIndexBase1())
398            indexBase = 1;
399
400        HObject hobject = null;
401
402        if (map != null) {
403            hobject = (HObject) map.get(ViewProperties.DATA_VIEW_KEY.OBJECT);
404            bitmask = (BitSet) map.get(ViewProperties.DATA_VIEW_KEY.BITMASK);
405            bitmaskOP = (BITMASK_OP) map.get(ViewProperties.DATA_VIEW_KEY.BITMASKOP);
406
407            Boolean b = (Boolean) map.get(ViewProperties.DATA_VIEW_KEY.CONVERTBYTE);
408            if (b != null)
409                convertByteData = b.booleanValue();
410
411            b = (Boolean) map.get(ViewProperties.DATA_VIEW_KEY.INDEXBASE1);
412            if (b != null) {
413                if (b.booleanValue())
414                    indexBase = 1;
415                else
416                    indexBase = 0;
417            }
418        }
419
420        if (hobject == null) {
421            hobject = theView.getTreeView().getCurrentObject();
422        }
423
424        if ((hobject == null) || !(hobject instanceof ScalarDS)) {
425            viewer.showError("Display data in image failed for - " + hobject);
426            return;
427        }
428
429        dataset = (ScalarDS) hobject;
430        dataRange = dataset.getImageDataRange();
431        if (dataRange == null) {
432            dataRange = new double[2];
433            dataRange[0] = dataRange[1] = 0;
434            if (dataset.getDatatype().getDatatypeSize() == 1
435                    && !convertByteData) {
436                dataRange[1] = 255; // byte image data rang = [0, 255]
437            }
438        }
439        else {
440            if (dataRange[0] < dataRange[1])
441                convertByteData = true;
442        }
443
444        if (image == null) {
445            image = getImage();
446        }
447
448        if (image == null) {
449            viewer.showError("Loading image failed - " + dataset.getName());
450            dataset = null;
451            return;
452        }
453
454        originalRange[0] = dataRange[0];
455        originalRange[1] = dataRange[1];
456
457        // set title
458        StringBuilder sb = new StringBuilder(hobject.getName());
459        sb.append("  at  ")
460          .append(hobject.getPath())
461          .append("  [")
462          .append(dataset.getFileFormat().getName())
463          .append("  in  ")
464          .append(dataset.getFileFormat().getParent())
465          .append("]");
466
467        frameTitle = sb.toString();
468        shell.setText(sb.toString());
469
470        // setup subset information
471        int rank = dataset.getRank();
472        int[] selectedIndex = dataset.getSelectedIndex();
473        long[] count = dataset.getSelectedDims();
474        long[] stride = dataset.getStride();
475        long[] dims = dataset.getDims();
476        long[] start = dataset.getStartDims();
477        int n = Math.min(3, rank);
478
479        if (rank > 2) {
480            curFrame = start[selectedIndex[2]] + indexBase;
481            maxFrame = (indexBase == 1) ? dims[selectedIndex[2]] : dims[selectedIndex[2]] - 1;
482        }
483
484        sb.append(" [ dims");
485        sb.append(selectedIndex[0]);
486        for (int i = 1; i < n; i++) {
487            sb.append("x");
488            sb.append(selectedIndex[i]);
489        }
490        sb.append(", start");
491        sb.append(start[selectedIndex[0]]);
492        for (int i = 1; i < n; i++) {
493            sb.append("x");
494            sb.append(start[selectedIndex[i]]);
495        }
496        sb.append(", count");
497        sb.append(count[selectedIndex[0]]);
498        for (int i = 1; i < n; i++) {
499            sb.append("x");
500            sb.append(count[selectedIndex[i]]);
501        }
502        sb.append(", stride");
503        sb.append(stride[selectedIndex[0]]);
504        for (int i = 1; i < n; i++) {
505            sb.append("x");
506            sb.append(stride[selectedIndex[i]]);
507        }
508        sb.append(" ] ");
509
510        viewer.showStatus(sb.toString());
511
512        shell.setMenuBar(createMenuBar());
513
514        // Add toolbar for Histogram, Frame selection, etc.
515        ToolBar bar = createToolbar(shell);
516        bar.setSize(shell.getSize().x, 30);
517        bar.setLocation(0, 0);
518
519        String originTag = "(0,0)";
520        if (ViewProperties.isIndexBase1())
521            originTag = "(1,1)";
522
523        // Create main component region
524        org.eclipse.swt.widgets.Group group = new org.eclipse.swt.widgets.Group(shell, SWT.NONE);
525        group.setFont(curFont);
526        group.setLayout(new GridLayout(2, false));
527        group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
528
529        if (imageOrigin == Origin.UPPER_LEFT || imageOrigin == Origin.UPPER_RIGHT) {
530            imageOriginLabel = new Label(group, SWT.NONE);
531            imageOriginLabel.setText(originTag);
532            imageOriginLabel.setLayoutData(
533                    new GridData(
534                            (imageOrigin == Origin.UPPER_LEFT || imageOrigin == Origin.LOWER_LEFT) ? SWT.BEGINNING : SWT.END,
535                            SWT.FILL, true, false, (imagePalette == null) ? 2 : 1, 1));
536
537            /* Dummy label to fill space in second column */
538            if (imagePalette != null)
539                new Label(group, SWT.NONE);
540        }
541
542        imageScroller = new ScrolledComposite(group, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
543        imageScroller.getHorizontalBar().setIncrement(50);
544        imageScroller.getVerticalBar().setIncrement(50);
545        imageScroller.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
546        imageScroller.setFont(curFont);
547
548        imageComponent = new ImageComponent(imageScroller, SWT.DOUBLE_BUFFERED, image);
549        imageScroller.setContent(imageComponent);
550
551        if (imageOrigin == Origin.LOWER_LEFT)
552            flip(FLIP_VERTICAL);
553        else if (imageOrigin == Origin.UPPER_RIGHT)
554            flip(FLIP_HORIZONTAL);
555        if (imageOrigin == Origin.LOWER_RIGHT) {
556            rotate(ROTATE_CW_90);
557            rotate(ROTATE_CW_90);
558        }
559
560        // add palette canvas to show the palette
561        if (imagePalette != null) {
562            paletteComponent = new PaletteComponent(group, SWT.DOUBLE_BUFFERED, imagePalette, dataRange);
563        }
564        else {
565            // Make ImageComponent take entire width
566            imageScroller.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
567        }
568
569        if (imageOrigin == Origin.LOWER_LEFT || imageOrigin == Origin.LOWER_RIGHT) {
570            imageOriginLabel = new Label(group, SWT.NONE);
571            imageOriginLabel.setText(originTag);
572            imageOriginLabel.setLayoutData(
573                    new GridData(
574                            (imageOrigin == Origin.UPPER_LEFT || imageOrigin == Origin.LOWER_LEFT) ? SWT.BEGINNING : SWT.END,
575                            SWT.FILL, true, false, (imagePalette == null) ? 2 : 1, 1));
576
577            /* Dummy label to fill space in second column */
578            if (imagePalette != null)
579                new Label(group, SWT.NONE);
580        }
581
582        // Add the text field to display pixel data
583        valueField = new Text(group, SWT.BORDER | SWT.SINGLE);
584        valueField.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1));
585        valueField.setEditable(false);
586        valueField.setFont(curFont);
587        setValueVisible(showValues);
588
589        shell.pack();
590
591        int width = 700 + (ViewProperties.getFontSize() - 12) * 15;
592        int height = 500 + (ViewProperties.getFontSize() - 12) * 10;
593        shell.setSize(width, height);
594
595        viewer.addDataView(this);
596
597        shell.open();
598    }
599
600    private Menu createMenuBar() {
601        Menu menuBar = new Menu(shell, SWT.BAR);
602
603        MenuItem item = new MenuItem(menuBar, SWT.CASCADE);
604        item.setText("Image");
605
606        Menu menu = new Menu(item);
607        item.setMenu(menu);
608
609        item = new MenuItem(menu, SWT.CASCADE);
610        item.setText("Save Image As");
611
612        Menu saveAsMenu = new Menu(item);
613        item.setMenu(saveAsMenu);
614
615        item = new MenuItem(saveAsMenu, SWT.PUSH);
616        item.setText("JPEG");
617        item.addSelectionListener(new SelectionAdapter() {
618            @Override
619            public void widgetSelected(SelectionEvent e) {
620                String filetype = Tools.FILE_TYPE_JPEG;
621
622                try {
623                    saveImageAs(filetype);
624                }
625                catch (Exception ex) {
626                    shell.getDisplay().beep();
627                    Tools.showError(shell, "Save", ex.getMessage());
628                }
629            }
630        });
631
632        /*
633         * ImageIO does not support tiff by default
634         */
635        /**
636         * item = new MenuItem(saveAsMenu, SWT.PUSH); item.setText("TIFF"); item.addSelectionListener(new
637         * SelectionAdapter() { public void widgetSelected(SelectionEvent e) { String filetype =
638         * Tools.FILE_TYPE_TIFF;
639         *
640         * try { saveImageAs(filetype); } catch (Exception ex) { shell.getDisplay().beep();
641         * Tools.showError(shell, "Save", ex.getMessage()); } } });
642         */
643
644        item = new MenuItem(saveAsMenu, SWT.PUSH);
645        item.setText("PNG");
646        item.addSelectionListener(new SelectionAdapter() {
647            @Override
648            public void widgetSelected(SelectionEvent e) {
649                String filetype = Tools.FILE_TYPE_PNG;
650
651                try {
652                    saveImageAs(filetype);
653                }
654                catch (Exception ex) {
655                    Tools.showError(shell, "Save", ex.getMessage());
656                }
657            }
658        });
659
660        item = new MenuItem(saveAsMenu, SWT.PUSH);
661        item.setText("GIF");
662        item.addSelectionListener(new SelectionAdapter() {
663            @Override
664            public void widgetSelected(SelectionEvent e) {
665                String filetype = Tools.FILE_TYPE_GIF;
666
667                try {
668                    saveImageAs(filetype);
669                }
670                catch (Exception ex) {
671                    Tools.showError(shell, "Save", ex.getMessage());
672                }
673            }
674        });
675
676        item = new MenuItem(saveAsMenu, SWT.PUSH);
677        item.setText("BMP");
678        item.addSelectionListener(new SelectionAdapter() {
679            @Override
680            public void widgetSelected(SelectionEvent e) {
681                String filetype = Tools.FILE_TYPE_BMP;
682
683                try {
684                    saveImageAs(filetype);
685                }
686                catch (Exception ex) {
687                    Tools.showError(shell, "Save", ex.getMessage());
688                }
689            }
690        });
691
692        new MenuItem(menu, SWT.SEPARATOR);
693
694        item = new MenuItem(menu, SWT.PUSH);
695        item.setText("Write Selection to Image");
696        item.setEnabled(!dataset.getFileFormat().isReadOnly());
697        item.addSelectionListener(new SelectionAdapter() {
698            @Override
699            public void widgetSelected(SelectionEvent e) {
700                writeSelectionToImage();
701            }
702        });
703
704        rotateRelatedItems.add(item);
705
706        new MenuItem(menu, SWT.SEPARATOR);
707
708        item = new MenuItem(menu, SWT.PUSH);
709        item.setText("Change Palette");
710        item.setEnabled(!isTrueColor);
711        item.addSelectionListener(new SelectionAdapter() {
712            @Override
713            public void widgetSelected(SelectionEvent e) {
714                showColorTable();
715            }
716        });
717
718        item = new MenuItem(menu, SWT.PUSH);
719        item.setText("Import Palette");
720        item.setEnabled(!isTrueColor);
721        item.addSelectionListener(new SelectionAdapter() {
722            @Override
723            public void widgetSelected(SelectionEvent e) {
724                FileDialog fChooser = new FileDialog(shell, SWT.OPEN);
725                fChooser.setFilterPath(ViewProperties.getWorkDir());
726
727                fChooser.setFilterExtensions(new String[] {"*"});
728                fChooser.setFilterNames(new String[] {"All Files"});
729                fChooser.setFilterIndex(0);
730
731                if(fChooser.open() == null)
732                    return;
733
734                File chosenFile = new File(fChooser.getFilterPath() + File.separator + fChooser.getFileName());
735                if(chosenFile == null || !chosenFile.exists() || chosenFile.isDirectory())
736                    return;
737
738                ArrayList<String> palList = (ArrayList<String>) ViewProperties.getPaletteList();
739                String palPath = chosenFile.getAbsolutePath();
740                if (!palList.contains(palPath))
741                    palList.add(palPath);
742            }
743        });
744
745        item = new MenuItem(menu, SWT.PUSH);
746        item.setText("Export Palette");
747        item.setEnabled(!isTrueColor);
748        item.addSelectionListener(new SelectionAdapter() {
749            @Override
750            public void widgetSelected(SelectionEvent e) {
751                if (imagePalette == null)
752                    return;
753
754                String workDir = ViewProperties.getWorkDir() + File.separator;
755                FileDialog fChooser = new FileDialog(shell, SWT.OPEN);
756                fChooser.setFilterPath(workDir);
757
758                fChooser.setFilterExtensions(new String[] {"*", "*.lut"});
759                fChooser.setFilterNames(new String[] {"All Files", "Color Lookup Table"});
760                fChooser.setFilterIndex(1);
761
762                File pfile = Tools.checkNewFile(workDir, ".lut");
763
764                fChooser.setFileName(pfile.getName());
765
766                if (fChooser.open() == null)
767                    return;
768
769                File chosenFile = new File(fChooser.getFilterPath() + File.separator + fChooser.getFileName());
770                if (chosenFile == null || chosenFile.isDirectory())
771                    return;
772
773                if (chosenFile.exists()) {
774                    int answer = SWT.NO;
775                    if(Tools.showConfirm(shell, "Export", "File exists. Do you want to replace it ?"))
776                        answer = SWT.YES;
777
778                    if (answer == SWT.NO)
779                        return;
780                }
781
782                PrintWriter out = null;
783
784                try {
785                    out = new PrintWriter(new BufferedWriter(new FileWriter(chosenFile)));
786                }
787                catch (Exception ex) {
788                    out = null;
789                }
790
791                if (out == null)
792                    return;
793
794                int cols = 3;
795                int rows = 256;
796                int rgb = 0;
797                for (int i=0; i<rows; i++) {
798                    out.print(i);
799                    for (int j=0; j<cols; j++) {
800                        out.print(' ');
801                        rgb = imagePalette[j][i];
802                        if (rgb<0) rgb += 256;
803                        out.print(rgb);
804                    }
805                    out.println();
806                }
807
808                out.flush();
809                out.close();
810            }
811        });
812
813        new MenuItem(menu, SWT.SEPARATOR);
814
815        item = new MenuItem(menu, SWT.PUSH);
816        item.setText("Set Value Range");
817        item.setEnabled(!isTrueColor);
818        item.addSelectionListener(new SelectionAdapter() {
819            @Override
820            public void widgetSelected(SelectionEvent e) {
821                if (originalRange == null || originalRange[0] == originalRange[1])
822                    return;
823
824                // Call only once
825                if (dataDist == null) {
826                    dataDist = new int[256];
827                    Tools.findDataDist(data, dataDist, originalRange);
828                }
829
830                DataRangeDialog drd = new DataRangeDialog(shell, SWT.NONE, dataRange, originalRange, dataDist);
831                drd.open();
832
833                double[] drange = drd.getRange();
834
835                if ((drange == null)
836                        || (drange[0] == drange[1])
837                        || ((drange[0] == dataRange[0]) && (drange[1] == dataRange[1]))) {
838                    return;
839                }
840
841                applyDataRange(drange);
842            }
843        });
844
845        new MenuItem(menu, SWT.SEPARATOR);
846
847        item = new MenuItem(menu, SWT.PUSH);
848        item.setText("Show Histogram");
849        item.setEnabled(!isTrueColor);
850        item.addSelectionListener(new SelectionAdapter() {
851            @Override
852            public void widgetSelected(SelectionEvent e) {
853                showHistogram();
854            }
855        });
856        rotateRelatedItems.add(item);
857
858        new MenuItem(menu, SWT.SEPARATOR);
859
860        item = new MenuItem(menu, SWT.PUSH);
861        item.setText("Zoom In");
862        item.addSelectionListener(new SelectionAdapter() {
863            @Override
864            public void widgetSelected(SelectionEvent e) {
865                zoomIn();
866            }
867        });
868
869        item = new MenuItem(menu, SWT.PUSH);
870        item.setText("Zoom Out");
871        item.addSelectionListener(new SelectionAdapter() {
872            @Override
873            public void widgetSelected(SelectionEvent e) {
874                zoomOut();
875            }
876        });
877
878        new MenuItem(menu, SWT.SEPARATOR);
879
880        item = new MenuItem(menu, SWT.CASCADE);
881        item.setText("Flip Image");
882
883        Menu flipMenu = new Menu(item);
884        item.setMenu(flipMenu);
885
886        item = new MenuItem(flipMenu, SWT.PUSH);
887        item.setText("Horizontal");
888        item.addSelectionListener(new SelectionAdapter() {
889            @Override
890            public void widgetSelected(SelectionEvent e) {
891                flip(FLIP_HORIZONTAL);
892            }
893        });
894
895        item = new MenuItem(flipMenu, SWT.PUSH);
896        item.setText("Vertical");
897        item.addSelectionListener(new SelectionAdapter() {
898            @Override
899            public void widgetSelected(SelectionEvent e) {
900                flip(FLIP_VERTICAL);
901            }
902        });
903
904        item = new MenuItem(menu, SWT.CASCADE);
905        item.setText("Rotate Image");
906
907        Menu rotateMenu = new Menu(item);
908        item.setMenu(rotateMenu);
909
910        char t = 186;
911
912        item = new MenuItem(rotateMenu, SWT.PUSH);
913        item.setText("90" + t + " CW");
914        item.addSelectionListener(new SelectionAdapter() {
915            @Override
916            public void widgetSelected(SelectionEvent e) {
917                rotate(ROTATE_CW_90);
918
919                int n = rotateRelatedItems.size();
920                for (int i = 0; i < n; i++) {
921                    boolean itemState = (rotateCount == 0);
922                    ((MenuItem) rotateRelatedItems.get(i)).setEnabled(itemState);
923                }
924            }
925        });
926
927        item = new MenuItem(rotateMenu, SWT.PUSH);
928        item.setText("90" + t + " CCW");
929        item.addSelectionListener(new SelectionAdapter() {
930            @Override
931            public void widgetSelected(SelectionEvent e) {
932                rotate(ROTATE_CCW_90);
933
934                int n = rotateRelatedItems.size();
935                for (int i = 0; i < n; i++) {
936                    boolean itemState = (rotateCount == 0);
937                    ((MenuItem) rotateRelatedItems.get(i)).setEnabled(itemState);
938                }
939            }
940        });
941
942        new MenuItem(menu, SWT.SEPARATOR);
943
944        item = new MenuItem(menu, SWT.PUSH);
945        item.setText("Brightness/Contrast");
946        item.addSelectionListener(new SelectionAdapter() {
947            @Override
948            public void widgetSelected(SelectionEvent e) {
949                if (contrastSlider == null)
950                    contrastSlider = new ContrastSlider(shell, SWT.NONE, image.getSource());
951                contrastSlider.open();
952            }
953        });
954
955        item = new MenuItem(menu, SWT.CASCADE);
956        item.setText("Contour");
957
958        Menu contourMenu = new Menu(item);
959        item.setMenu(contourMenu);
960
961        for (int i = 3; i < 10; i += 2) {
962            item = new MenuItem(contourMenu, SWT.PUSH);
963            item.setText(String.valueOf(i));
964            item.addSelectionListener(new SelectionAdapter() {
965                @Override
966                public void widgetSelected(SelectionEvent e) {
967                    MenuItem item = (MenuItem) e.widget;
968                    contour(Integer.parseInt(item.getText()));
969                }
970            });
971        }
972
973        new MenuItem(menu, SWT.SEPARATOR);
974
975        item = new MenuItem(menu, SWT.PUSH);
976        item.setText("Show Animation");
977        item.setEnabled(is3D);
978        item.addSelectionListener(new SelectionAdapter() {
979            @Override
980            public void widgetSelected(SelectionEvent e) {
981                shell.setCursor(display.getSystemCursor(SWT.CURSOR_WAIT));
982
983                new Animation(shell, SWT.DOUBLE_BUFFERED, dataset).open();
984
985                shell.setCursor(null);
986            }
987        });
988
989        item = new MenuItem(menu, SWT.CASCADE);
990        item.setText("Animation Speed (frames/second)");
991        item.setEnabled(is3D);
992
993        Menu animationSpeedMenu = new Menu(item);
994        item.setMenu(animationSpeedMenu);
995
996        for (int i = 2; i < 12; i += 2) {
997            item = new MenuItem(animationSpeedMenu, SWT.PUSH);
998            item.setText(String.valueOf(i));
999            item.addSelectionListener(new SelectionAdapter() {
1000                @Override
1001                public void widgetSelected(SelectionEvent e) {
1002                    MenuItem item = (MenuItem) e.widget;
1003                    animationSpeed = Integer.parseInt(item.getText());
1004                }
1005            });
1006        }
1007
1008        new MenuItem(menu, SWT.SEPARATOR);
1009
1010        item = new MenuItem(menu, SWT.CHECK);
1011        item.setText("Show Values");
1012        item.setSelection(showValues);
1013        item.addSelectionListener(new SelectionAdapter() {
1014            @Override
1015            public void widgetSelected(SelectionEvent e) {
1016                showValues = !showValues;
1017                setValueVisible(showValues);
1018            }
1019        });
1020        rotateRelatedItems.add(item);
1021
1022        item = new MenuItem(menu, SWT.PUSH);
1023        item.setText("Show Statistics");
1024        item.addSelectionListener(new SelectionAdapter() {
1025            @Override
1026            public void widgetSelected(SelectionEvent e) {
1027                try {
1028                    double[] minmax = new double[2];
1029                    double[] stat = new double[2];
1030
1031                    Object theData = null;
1032                    theData = getSelectedData();
1033
1034                    if (theData == null)
1035                        theData = data;
1036
1037                    Tools.findMinMax(theData, minmax, dataset.getFillValue());
1038                    if (Tools.computeStatistics(theData, stat, dataset.getFillValue()) > 0) {
1039                        String statistics = "Min                      = "
1040                                + minmax[0] + "\nMax                      = "
1041                                + minmax[1] + "\nMean                     = "
1042                                + stat[0] + "\nStandard deviation = " + stat[1];
1043
1044                        Tools.showInformation(shell, "Statistics", statistics);
1045                    }
1046                }
1047                catch (Exception ex) {
1048                    shell.getDisplay().beep();
1049                    Tools.showError(shell, "Statistics", ex.getMessage());
1050                }
1051            }
1052        });
1053
1054        new MenuItem(menu, SWT.SEPARATOR);
1055
1056        item = new MenuItem(menu, SWT.PUSH);
1057        item.setText("Select All");
1058        item.addSelectionListener(new SelectionAdapter() {
1059            @Override
1060            public void widgetSelected(SelectionEvent e) {
1061                try {
1062                    selectAll();
1063                }
1064                catch (Exception ex) {
1065                    shell.getDisplay().beep();
1066                    Tools.showError(shell, "Select", ex.getMessage());
1067                }
1068            }
1069        });
1070
1071        new MenuItem(menu, SWT.SEPARATOR);
1072
1073        item = new MenuItem(menu, SWT.PUSH);
1074        item.setText("Close");
1075        item.addSelectionListener(new SelectionAdapter() {
1076            @Override
1077            public void widgetSelected(SelectionEvent e) {
1078                shell.dispose();
1079            }
1080        });
1081
1082        return menuBar;
1083    }
1084
1085    private ToolBar createToolbar(final Shell shell) {
1086        ToolBar toolbar = new ToolBar(shell, SWT.HORIZONTAL | SWT.RIGHT | SWT.BORDER);
1087        toolbar.setFont(curFont);
1088        toolbar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
1089
1090        // Chart button
1091        ToolItem item = new ToolItem(toolbar, SWT.PUSH);
1092        item.setImage(ViewProperties.getChartIcon());
1093        item.setToolTipText("Histogram");
1094        item.setEnabled(!isTrueColor);
1095        item.addSelectionListener(new SelectionAdapter() {
1096            @Override
1097            public void widgetSelected(SelectionEvent e) {
1098                showHistogram();
1099            }
1100        });
1101
1102        // Palette button
1103        item = new ToolItem(toolbar, SWT.PUSH);
1104        item.setImage(ViewProperties.getPaletteIcon());
1105        item.setToolTipText("Palette");
1106        item.setEnabled(!isTrueColor);
1107        item.addSelectionListener(new SelectionAdapter() {
1108            @Override
1109            public void widgetSelected(SelectionEvent e) {
1110                showColorTable();
1111            }
1112        });
1113
1114        // Brightness button
1115        item = new ToolItem(toolbar, SWT.PUSH);
1116        item.setImage(ViewProperties.getBrightIcon());
1117        item.setToolTipText("Brightness");
1118        item.addSelectionListener(new SelectionAdapter() {
1119            @Override
1120            public void widgetSelected(SelectionEvent e) {
1121                if (contrastSlider == null)
1122                    contrastSlider = new ContrastSlider(shell, SWT.NONE, image.getSource());
1123                contrastSlider.open();
1124            }
1125        });
1126
1127        // Zoom in button
1128        item = new ToolItem(toolbar, SWT.PUSH);
1129        item.setImage(ViewProperties.getZoominIcon());
1130        item.setToolTipText("Zoom In");
1131        item.addSelectionListener(new SelectionAdapter() {
1132            @Override
1133            public void widgetSelected(SelectionEvent e) {
1134                zoomIn();
1135            }
1136        });
1137
1138        // Zoom out button
1139        item = new ToolItem(toolbar, SWT.PUSH);
1140        item.setImage(ViewProperties.getZoomoutIcon());
1141        item.setToolTipText("Zoom Out");
1142        item.addSelectionListener(new SelectionAdapter() {
1143            @Override
1144            public void widgetSelected(SelectionEvent e) {
1145                zoomOut();
1146            }
1147        });
1148
1149        if (is3D) {
1150            new ToolItem(toolbar, SWT.SEPARATOR).setWidth(20);
1151
1152            // First frame button
1153            item = new ToolItem(toolbar, SWT.PUSH);
1154            item.setImage(ViewProperties.getFirstIcon());
1155            item.setToolTipText("First Page");
1156            item.addSelectionListener(new SelectionAdapter() {
1157                @Override
1158                public void widgetSelected(SelectionEvent e) {
1159                    try {
1160                        shell.setCursor(display.getSystemCursor(SWT.CURSOR_WAIT));
1161
1162                        firstPage();
1163                    }
1164                    finally {
1165                        shell.setCursor(null);
1166                    }
1167                }
1168            });
1169
1170            // Previous frame button
1171            item = new ToolItem(toolbar, SWT.PUSH);
1172            item.setImage(ViewProperties.getPreviousIcon());
1173            item.setToolTipText("Previous Page");
1174            item.addSelectionListener(new SelectionAdapter() {
1175                @Override
1176                public void widgetSelected(SelectionEvent e) {
1177                    try {
1178                        shell.setCursor(display.getSystemCursor(SWT.CURSOR_WAIT));
1179
1180                        previousPage();
1181                    }
1182                    finally {
1183                        shell.setCursor(null);
1184                    }
1185                }
1186            });
1187
1188            ToolItem separator = new ToolItem(toolbar, SWT.SEPARATOR);
1189
1190            frameField = new Text(toolbar, SWT.SINGLE | SWT.BORDER | SWT.CENTER);
1191            frameField.setFont(curFont);
1192            frameField.setText(String.valueOf(curFrame));
1193            frameField.addTraverseListener(new TraverseListener() {
1194                @Override
1195                public void keyTraversed(TraverseEvent e) {
1196                    if (e.detail == SWT.TRAVERSE_RETURN) {
1197                        try {
1198                            shell.setCursor(display.getSystemCursor(SWT.CURSOR_WAIT));
1199
1200                            int page = 0;
1201
1202                            try {
1203                                page = Integer.parseInt(frameField.getText()
1204                                        .trim()) - indexBase;
1205                            }
1206                            catch (Exception ex) {
1207                                page = -1;
1208                            }
1209
1210                            gotoPage(page);
1211                        }
1212                        finally {
1213                            shell.setCursor(null);
1214                        }
1215                    }
1216                }
1217            });
1218
1219            frameField.pack();
1220
1221            separator.setWidth(frameField.getSize().x + 30);
1222            separator.setControl(frameField);
1223
1224            separator = new ToolItem(toolbar, SWT.SEPARATOR);
1225
1226            Text maxFrameText = new Text(toolbar, SWT.SINGLE | SWT.BORDER | SWT.CENTER);
1227            maxFrameText.setFont(curFont);
1228            maxFrameText.setText(String.valueOf(maxFrame));
1229            maxFrameText.setEditable(false);
1230            maxFrameText.setEnabled(false);
1231
1232            maxFrameText.pack();
1233
1234            separator.setWidth(maxFrameText.getSize().x + 30);
1235            separator.setControl(maxFrameText);
1236
1237            new ToolItem(toolbar, SWT.SEPARATOR).setWidth(10);
1238
1239            // Next frame button
1240            item = new ToolItem(toolbar, SWT.PUSH);
1241            item.setImage(ViewProperties.getNextIcon());
1242            item.setToolTipText("Next Page");
1243            item.addSelectionListener(new SelectionAdapter() {
1244                @Override
1245                public void widgetSelected(SelectionEvent e) {
1246                    try {
1247                        shell.setCursor(display.getSystemCursor(SWT.CURSOR_WAIT));
1248
1249                        nextPage();
1250                    }
1251                    finally {
1252                        shell.setCursor(null);
1253                    }
1254                }
1255            });
1256
1257            // Last frame button
1258            item = new ToolItem(toolbar, SWT.PUSH);
1259            item.setImage(ViewProperties.getLastIcon());
1260            item.setToolTipText("Last Page");
1261            item.addSelectionListener(new SelectionAdapter() {
1262                @Override
1263                public void widgetSelected(SelectionEvent e) {
1264                    try {
1265                        shell.setCursor(display.getSystemCursor(SWT.CURSOR_WAIT));
1266
1267                        lastPage();
1268                    }
1269                    finally {
1270                        shell.setCursor(null);
1271                    }
1272                }
1273            });
1274
1275            // Animation button
1276            item = new ToolItem(toolbar, SWT.PUSH);
1277            item.setImage(ViewProperties.getAnimationIcon());
1278            item.setToolTipText("View Animation");
1279            item.addSelectionListener(new SelectionAdapter() {
1280                @Override
1281                public void widgetSelected(SelectionEvent e) {
1282                    shell.setCursor(display.getSystemCursor(SWT.CURSOR_WAIT));
1283                    new Animation(shell, SWT.DOUBLE_BUFFERED, dataset).open();
1284                    shell.setCursor(null);
1285                }
1286            });
1287        }
1288
1289        return toolbar;
1290    }
1291
1292    // Implementing DataObserver.
1293    private void previousPage() {
1294        int rank = dataset.getRank();
1295
1296        if (rank < 3)
1297            return;
1298
1299        int[] selectedIndex = dataset.getSelectedIndex();
1300        long[] selectedDims = dataset.getSelectedDims();
1301
1302        if (selectedDims[selectedIndex[2]] > 1)
1303            return; // it is a true color image with three color components
1304
1305        long[] start = dataset.getStartDims();
1306        long idx = start[selectedIndex[2]];
1307        if (idx == 0)
1308            return; // current page is the first page
1309
1310        gotoPage(start[selectedIndex[2]] - 1);
1311    }
1312
1313    // Implementing DataObserver.
1314    private void nextPage() {
1315        int rank = dataset.getRank();
1316
1317        if (rank < 3)
1318            return;
1319
1320        int[] selectedIndex = dataset.getSelectedIndex();
1321        long[] selectedDims = dataset.getSelectedDims();
1322
1323        if (selectedDims[selectedIndex[2]] > 1)
1324            return; // it is a true color image with three color components
1325
1326        long[] start = dataset.getStartDims();
1327        long[] dims = dataset.getDims();
1328        long idx = start[selectedIndex[2]];
1329        if (idx == dims[selectedIndex[2]] - 1)
1330            return; // current page is the last page
1331
1332        gotoPage(start[selectedIndex[2]] + 1);
1333    }
1334
1335    // Implementing DataObserver.
1336    private void firstPage() {
1337        int rank = dataset.getRank();
1338
1339        if (rank < 3)
1340            return;
1341
1342        int[] selectedIndex = dataset.getSelectedIndex();
1343        long[] selectedDims = dataset.getSelectedDims();
1344
1345        if (selectedDims[selectedIndex[2]] > 1)
1346            return; // it is a true color image with three color components
1347
1348        long[] start = dataset.getStartDims();
1349        long idx = start[selectedIndex[2]];
1350        if (idx == 0)
1351            return; // current page is the first page
1352
1353        gotoPage(0);
1354    }
1355
1356    // Implementing DataObserver.
1357    private void lastPage() {
1358        int rank = dataset.getRank();
1359
1360        if (rank < 3)
1361            return;
1362
1363        int[] selectedIndex = dataset.getSelectedIndex();
1364        long[] selectedDims = dataset.getSelectedDims();
1365
1366        if (selectedDims[selectedIndex[2]] > 1)
1367            return; // it is a true color image with three color components
1368
1369        long[] start = dataset.getStartDims();
1370        long[] dims = dataset.getDims();
1371        long idx = start[selectedIndex[2]];
1372        if (idx == dims[selectedIndex[2]] - 1)
1373            return; // current page is the last page
1374
1375        gotoPage(dims[selectedIndex[2]] - 1);
1376    }
1377
1378    @Override
1379    public Image getImage() {
1380        if (image != null) {
1381            return image;
1382        }
1383
1384        if (!dataset.isInited())
1385            dataset.init();
1386
1387        isTrueColor = dataset.isTrueColor();
1388        is3D = (dataset.getRank() > 2) && !dataset.isTrueColor();
1389
1390        try {
1391            if (isTrueColor)
1392                getTrueColorImage();
1393            else
1394                getIndexedImage();
1395        }
1396        catch (Exception ex) {
1397            shell.getDisplay().beep();
1398            Tools.showError(shell, "Select", "ImageView: " + shell.getText());
1399            return null;
1400        }
1401
1402        // set number type, ...
1403        if (data != null) {
1404            String cname = data.getClass().getName();
1405            NT = cname.charAt(cname.lastIndexOf('[') + 1);
1406        }
1407
1408        return image;
1409    }
1410
1411    /**
1412     * @throws Exception if a failure occurred
1413     * @throws OutOfMemoryError if memory is exhausted
1414     */
1415    private void getIndexedImage() throws Exception, OutOfMemoryError {
1416        if (imagePalette == null)
1417            imagePalette = dataset.getPalette();
1418
1419        boolean noPalette = false;
1420        boolean isLocalFile = dataset.getFileFormat().exists();
1421
1422        if (imagePalette == null) {
1423            noPalette = true;
1424            imagePalette = Tools.createGrayPalette();
1425            viewer.showStatus("\nNo attached palette found, default grey palette is used to display image");
1426        }
1427
1428        // Make sure entire dataset is not loaded when looking at 3D
1429        // datasets using the default display mode (double clicking the
1430        // data object)
1431        if (dataset.getRank() > 2)
1432            dataset.getSelectedDims()[dataset.getSelectedIndex()[2]] = 1;
1433
1434        data = dataset.getData();
1435        if ((bitmask != null) && Tools.applyBitmask(data, bitmask, bitmaskOP))
1436            doAutoGainContrast = false;
1437
1438        if (dataset.getDatatype().isInteger() || dataset.getDatatype().isChar()) {
1439            data = dataset.convertFromUnsignedC();
1440            isUnsignedConverted = true;
1441            doAutoGainContrast = doAutoGainContrast ||
1442                    (ViewProperties.isAutoContrast() && noPalette && isLocalFile);
1443        }
1444        else
1445            doAutoGainContrast = false;
1446
1447        boolean isAutoContrastFailed = true;
1448        if (doAutoGainContrast) {
1449            isAutoContrastFailed = (!computeAutoGainImageData(gainBias, null));
1450        }
1451
1452        long w = dataset.getWidth();
1453        long h = dataset.getHeight();
1454
1455        if (isAutoContrastFailed) {
1456            doAutoGainContrast = false;
1457            imageByteData = Tools.getBytes(data, dataRange, w, h, !dataset
1458                    .isDefaultImageOrder(), dataset.getFilteredImageValues(),
1459                    convertByteData, imageByteData, invalidValueIndex);
1460        }
1461        else if (dataRange!= null && dataRange[0]==dataRange[1]) {
1462            Tools.findMinMax(data, dataRange, null);
1463        }
1464
1465        image = createIndexedImage(imageByteData, imagePalette, w, h);
1466    }
1467
1468    /**
1469     * @throws Exception
1470     * @throws OutOfMemoryError
1471     */
1472    private void getTrueColorImage() throws Exception, OutOfMemoryError {
1473        isPlaneInterlace = (dataset.getInterlace() == ScalarDS.INTERLACE_PLANE);
1474
1475        long[] selected = dataset.getSelectedDims();
1476        long[] start = dataset.getStartDims();
1477        int[] selectedIndex = dataset.getSelectedIndex();
1478        long[] stride = dataset.getStride();
1479
1480        if (start.length > 2) {
1481            start[selectedIndex[2]] = 0;
1482            selected[selectedIndex[2]] = 3;
1483            stride[selectedIndex[2]] = 1;
1484        }
1485
1486        // reload data
1487        dataset.clearData();
1488        data = dataset.getData();
1489
1490        long w = dataset.getWidth();
1491        long h = dataset.getHeight();
1492
1493        // converts raw data to image data
1494        imageByteData = Tools.getBytes(data, dataRange, w, h, false, dataset.getFilteredImageValues(),
1495                imageByteData);
1496
1497        image = createTrueColorImage(imageByteData, isPlaneInterlace, (int)w, (int)h);
1498    }
1499
1500    /**
1501     * Compute image data from autogain
1502     *
1503     * @param gb the gain bias
1504     * @param range the contrast range to apply
1505     *
1506     * @return true if the image buffer is converted
1507     */
1508    private boolean computeAutoGainImageData(double[] gb, double[] range) {
1509        boolean retValue = true;
1510
1511        // data is unsigned short. Convert image byte data using auto-contrast
1512        // image algorithm
1513
1514        if (gainBias == null) { // calculate auto_gain only once
1515            gainBias = new double[2];
1516            Tools.autoContrastCompute(data, gainBias, dataset.getDatatype().isUnsigned());
1517        }
1518
1519        if (gb == null)
1520            gb = gainBias;
1521
1522        autoGainData = Tools.autoContrastApply(data, autoGainData, gb, range, dataset.getDatatype().isUnsigned());
1523
1524        if (autoGainData != null) {
1525            if ((imageByteData == null) || (imageByteData.length != Array.getLength(data))) {
1526                imageByteData = new byte[Array.getLength(data)];
1527            }
1528            retValue = (Tools.autoContrastConvertImageBuffer(autoGainData, imageByteData, true) >= 0);
1529        }
1530        else
1531            retValue = false;
1532
1533        if (gainBiasCurrent == null)
1534            gainBiasCurrent = new double[2];
1535
1536        gainBiasCurrent[0] = gb[0];
1537        gainBiasCurrent[1] = gb[1];
1538
1539        return retValue;
1540    }
1541
1542    // implementing ImageObserver
1543    private void zoomIn() {
1544        if (zoomFactor >= 1)
1545            zoomTo(zoomFactor + 1.0f);
1546        else
1547            zoomTo(zoomFactor + 0.125f);
1548    }
1549
1550    // implementing ImageObserver
1551    private void zoomOut() {
1552        if (zoomFactor > 1)
1553            zoomTo(zoomFactor - 1.0f);
1554        else
1555            zoomTo(zoomFactor - 0.125f);
1556    }
1557
1558    // implementing ImageObserver
1559    private void zoomTo(float zf) {
1560        if (zf > 8)
1561            zf = 8;
1562        else if (zf < 0.125)
1563            zf = 0.125f;
1564
1565        if (zoomFactor == zf)
1566            return; // no change in zooming
1567
1568        zoomFactor = zf;
1569
1570        Dimension imageSize = new Dimension(
1571                (int) (imageComponent.originalSize.width * zoomFactor),
1572                (int) (imageComponent.originalSize.height * zoomFactor)
1573                );
1574
1575        imageComponent.setImageSize(imageSize);
1576        imageComponent.redraw();
1577
1578        if ((zoomFactor > 0.99) && (zoomFactor < 1.01))
1579            shell.setText(frameTitle);
1580        else
1581            shell.setText(frameTitle + " - " + 100 * zoomFactor + "%");
1582    }
1583
1584    // implementing ImageObserver
1585    private void showColorTable() {
1586        if (imagePalette == null)
1587            return;
1588
1589        DataViewFactory paletteViewFactory = null;
1590        try {
1591            paletteViewFactory = DataViewFactoryProducer.getFactory(DataViewType.PALETTE);
1592        }
1593        catch (Exception ex) {
1594            log.debug("showColorTable(): error occurred while instantiating PaletteView factory class", ex);
1595            viewer.showError("Error occurred while instantiating PaletteView factory class");
1596            return;
1597        }
1598
1599        if (paletteViewFactory == null) {
1600            log.debug("showColorTable(): PaletteView factory is null");
1601            return;
1602        }
1603
1604        PaletteView theView;
1605        try {
1606            theView = paletteViewFactory.getPaletteView(shell, viewer, this);
1607
1608            if (theView == null) {
1609                log.debug("showColorTable(): error occurred while instantiating PaletteView class");
1610                viewer.showError("Error occurred while instantiating PaletteView class");
1611                Tools.showError(shell, "Show Palette", "Error occurred while instantiating PaletteView class");
1612            }
1613        }
1614        catch (ClassNotFoundException ex) {
1615            log.debug("showColorTable(): no suitable PaletteView class found");
1616            viewer.showError("Unable to find suitable PaletteView class for object '" + dataset.getName() + "'");
1617            Tools.showError(shell, "Show Palette", "Unable to find suitable PaletteView class for object '" + dataset.getName() + "'");
1618        }
1619    }
1620
1621    private void showHistogram() {
1622        Rectangle rec = imageComponent.selectedArea;
1623
1624        if (isTrueColor) {
1625            shell.getDisplay().beep();
1626            Tools.showError(shell, "Select", "Unsupported operation: unable to draw histogram for true color image.");
1627            return;
1628        }
1629
1630        if ((rec == null) || (rec.width <= 0) || (rec.height <= 0)) {
1631            shell.getDisplay().beep();
1632            Tools.showError(shell, "Select",
1633                    "No data for histogram.\nUse Shift+Mouse_drag to select an image area.");
1634            return;
1635        }
1636
1637        double[][] chartData = new double[1][256];
1638        for (int i = 0; i < 256; i++) {
1639            chartData[0][i] = 0.0;
1640        }
1641
1642        // Java only allows ints for array indices, may cause an issue with a dataset of width
1643        // larger than an int
1644        int w = (int) dataset.getWidth();
1645        int x0 = (int) (rec.x / zoomFactor);
1646        int y0 = (int) (rec.y / zoomFactor);
1647        int x = x0 + (int) (rec.width / zoomFactor);
1648        int y = y0 + (int) (rec.height / zoomFactor);
1649        int arrayIndex = 0;
1650        for (int i = y0; i < y; i++) {
1651            for (int j = x0; j < x; j++) {
1652                arrayIndex = imageByteData[i * w + j];
1653                if (arrayIndex < 0)
1654                    arrayIndex += 256;
1655                chartData[0][arrayIndex] += 1.0;
1656            }
1657        }
1658
1659        // Use original data range
1660        double[] xRange = originalRange;
1661        if (xRange == null || xRange[0] == xRange[1]) {
1662            xRange = new double[2];
1663            Tools.findMinMax(data, xRange, null);
1664        }
1665
1666        Chart cv = new Chart(shell, "Histogram - " + dataset.getPath()
1667                + dataset.getName() + " - by pixel index", Chart.HISTOGRAM,
1668                chartData, xRange, null);
1669        cv.open();
1670    }
1671
1672    /**
1673     * Selects the whole image.
1674     *
1675     * @throws Exception if a failure occurred
1676     */
1677    private void selectAll() throws Exception {
1678        imageComponent.selectAll();
1679    }
1680
1681    // implementing ImageObserver
1682    private void flip(int direction) {
1683        ImageFilter filter = new FlipFilter(direction);
1684
1685        if (applyImageFilter(filter)) {
1686            // toggle flip flag
1687            if (direction == FLIP_HORIZONTAL)
1688                isHorizontalFlipped = !isHorizontalFlipped;
1689            else
1690                isVerticalFlipped = !isVerticalFlipped;
1691        }
1692    }
1693
1694    // implementing ImageObserver
1695    private void rotate(int direction) {
1696        if ( !(direction == ROTATE_CW_90 || direction == ROTATE_CCW_90))
1697            return;
1698
1699        Rotate90Filter filter = new Rotate90Filter(direction);
1700        applyImageFilter(filter);
1701
1702        if (direction == ROTATE_CW_90) {
1703            rotateCount++;
1704            if (rotateCount == 4)
1705                rotateCount = 0;
1706        }
1707        else {
1708            rotateCount--;
1709            if (rotateCount == -4)
1710                rotateCount = 0;
1711        }
1712    }
1713
1714    // implementing ImageObserver
1715    private void contour(int level) {
1716        applyImageFilter(new ContourFilter(level));
1717    }
1718
1719    /** Apply contrast/brightness to unsigned short integer
1720     *
1721     * @param gb the gain bias
1722     * @param range the contrast range to apply
1723     */
1724    private void applyAutoGain(double[] gb, double[] range) {
1725        if (computeAutoGainImageData(gb, range)) {
1726            long w = dataset.getWidth();
1727            long h = dataset.getHeight();
1728            image = createIndexedImage(imageByteData, imagePalette, w, h);
1729            imageComponent.setImage(image);
1730            zoomTo(zoomFactor);
1731        }
1732    }
1733
1734    private void setValueVisible(boolean b) {
1735        valueField.setVisible(b);
1736
1737        GridData gridData = (GridData) valueField.getLayoutData();
1738
1739        if(!b)
1740            gridData.exclude = true;
1741        else
1742            gridData.exclude = false;
1743
1744        valueField.setLayoutData(gridData);
1745
1746        valueField.getParent().pack();
1747
1748        shell.pack();
1749    }
1750
1751    /** change alpha value for a given list of pixel locations
1752     *
1753     * @param img the image to adjust
1754     * @param alpha the alpha value
1755     * @param idx the list of indices to adjust
1756     *
1757     */
1758    private void adjustAlpha(BufferedImage img, int alpha, List<Integer> idx)
1759    {
1760        if (img == null || idx.isEmpty())
1761            return;
1762
1763        final int[] pixels = ( (DataBufferInt) img.getRaster().getDataBuffer() ).getData();
1764        int len = pixels.length;
1765
1766        alpha = alpha << 24;
1767        for (Integer i : idx) {
1768            if (i < len)
1769                pixels[i] = alpha | (pixels[i] & 0x00ffffff);
1770        }
1771    }
1772
1773    /**
1774     * Save the image to an image file.
1775     *
1776     * @param type
1777     *            the image type.
1778     *
1779     * @throws Exception
1780     *             if a failure occurred
1781     */
1782    private void saveImageAs(String type) throws Exception {
1783        if (image == null) {
1784            return;
1785        }
1786
1787        FileDialog fChooser = new FileDialog(shell, SWT.SAVE);
1788        fChooser.setFilterPath(dataset.getFileFormat().getParent());
1789        fChooser.setOverwrite(true);
1790
1791        DefaultFileFilter filter = null;
1792
1793        if (type.equals(Tools.FILE_TYPE_JPEG)) {
1794            filter = DefaultFileFilter.getFileFilterJPEG();
1795        } /**
1796           * else if (type.equals(Tools.FILE_TYPE_TIFF)) { filter = DefaultFileFilter.getFileFilterTIFF(); }
1797           */
1798        else if (type.equals(Tools.FILE_TYPE_PNG)) {
1799            filter = DefaultFileFilter.getFileFilterPNG();
1800        }
1801        else if (type.equals(Tools.FILE_TYPE_GIF)) {
1802            filter = DefaultFileFilter.getFileFilterGIF();
1803        }
1804        else if (type.equals(Tools.FILE_TYPE_BMP)) {
1805            filter = DefaultFileFilter.getFileFilterBMP();
1806        }
1807
1808        if (filter == null) {
1809            fChooser.setFilterExtensions(new String[] {"*"});
1810            fChooser.setFilterNames(new String[] {"All Files"});
1811            fChooser.setFilterIndex(0);
1812        }
1813        else {
1814            fChooser.setFilterExtensions(new String[] {"*", filter.getExtensions()});
1815            fChooser.setFilterNames(new String[] {"All Files", filter.getDescription()});
1816            fChooser.setFilterIndex(1);
1817        }
1818
1819        fChooser.setText("Save Current Image To " + type + " File --- " + dataset.getName());
1820
1821        File chosenFile = new File(dataset.getName() + "." + type.toLowerCase());
1822        fChooser.setFileName(chosenFile.getName());
1823
1824        String filename = fChooser.open();
1825
1826        if(filename == null) {
1827            return;
1828        }
1829
1830        chosenFile = new File(filename);
1831
1832        BufferedImage bi = null;
1833        try {
1834            bi = Tools.toBufferedImage(image);
1835
1836            // Convert JPG and BMP images to TYPE_INT_RGB so ImageIO.write succeeds
1837            if (bi.getType() == BufferedImage.TYPE_INT_ARGB &&
1838                    (type.equals("JPEG") || type.equals("BMP"))) {
1839                BufferedImage newImage = new BufferedImage(bi.getWidth(), bi.getHeight(), BufferedImage.TYPE_INT_RGB);
1840                Graphics g = newImage.createGraphics();
1841                g.drawImage(bi, 0, 0, Color.BLACK, null);
1842                g.dispose();
1843                bi = newImage;
1844            }
1845        }
1846        catch (OutOfMemoryError err) {
1847            shell.getDisplay().beep();
1848            Tools.showError(shell, "Save", err.getMessage());
1849            return;
1850        }
1851
1852        Tools.saveImageAs(bi, chosenFile, type);
1853
1854        viewer.showStatus("Current image saved to: " + chosenFile.getAbsolutePath());
1855
1856        try (RandomAccessFile rf = new RandomAccessFile(chosenFile, "r")) {
1857            long size = rf.length();
1858            viewer.showStatus("File size (bytes): " + size);
1859        }
1860        catch (Exception ex) {
1861            log.debug("File {} size:", chosenFile.getName(), ex);
1862        }
1863    }
1864
1865    // Implementing DataView.
1866    @Override
1867    public HObject getDataObject() {
1868        return dataset;
1869    }
1870
1871    @Override
1872    public byte[] getImageByteData() {
1873        return imageByteData;
1874    }
1875
1876    /**
1877     * Returns the selected data values.
1878     *
1879     * @return the selected data object.
1880     */
1881    @Override
1882    public Object getSelectedData() {
1883        Object selectedData = null;
1884
1885        int cols = imageComponent.originalSelectedArea.width;
1886        int rows = imageComponent.originalSelectedArea.height;
1887
1888        if ((cols <= 0) || (rows <= 0)) {
1889            return null; // no data is selected
1890        }
1891
1892        int size = cols * rows;
1893        if (isTrueColor) {
1894            size *= 3;
1895        }
1896
1897        if (NT == 'B') {
1898            selectedData = new byte[size];
1899        }
1900        else if (NT == 'S') {
1901            selectedData = new short[size];
1902        }
1903        else if (NT == 'I') {
1904            selectedData = new int[size];
1905        }
1906        else if (NT == 'J') {
1907            selectedData = new long[size];
1908        }
1909        else if (NT == 'F') {
1910            selectedData = new float[size];
1911        }
1912        else if (NT == 'D') {
1913            selectedData = new double[size];
1914        }
1915        else {
1916            return null;
1917        }
1918
1919        int r0 = imageComponent.originalSelectedArea.y;
1920        int c0 = imageComponent.originalSelectedArea.x;
1921        int w = imageComponent.originalSize.width;
1922        int h = imageComponent.originalSize.height;
1923
1924        // transfer location to the original coordinator
1925        if (isHorizontalFlipped) {
1926            c0 = w - 1 - c0 - cols;
1927        }
1928
1929        if (isVerticalFlipped) {
1930            r0 = h - 1 - r0 - rows;
1931        }
1932
1933        int idxSrc = 0;
1934        int idxDst = 0;
1935        if (isTrueColor) {
1936            int imageSize = w * h;
1937            if (isPlaneInterlace) {
1938                for (int j = 0; j < 3; j++) {
1939                    int plane = imageSize * j;
1940                    for (int i = 0; i < rows; i++) {
1941                        idxSrc = plane + (r0 + i) * w + c0;
1942                        System.arraycopy(data, idxSrc, selectedData, idxDst,
1943                                cols);
1944                        idxDst += cols;
1945                    }
1946                }
1947            }
1948            else {
1949                int numberOfDataPoints = cols * 3;
1950                for (int i = 0; i < rows; i++) {
1951                    idxSrc = (r0 + i) * w + c0;
1952                    System.arraycopy(data, idxSrc * 3, selectedData, idxDst,
1953                            numberOfDataPoints);
1954                    idxDst += numberOfDataPoints;
1955                }
1956            }
1957        }
1958        else { // indexed image
1959            for (int i = 0; i < rows; i++) {
1960                idxSrc = (r0 + i) * w + c0;
1961                System.arraycopy(data, idxSrc, selectedData, idxDst, cols);
1962                idxDst += cols;
1963            }
1964        }
1965
1966        return selectedData;
1967    }
1968
1969    /**
1970     * returns the selected area of the image
1971     *
1972     * @return the rectangle of the selected image area.
1973     */
1974    @Override
1975    public Rectangle getSelectedArea() {
1976        return imageComponent.originalSelectedArea;
1977    }
1978
1979    /** @return true if the image is a truecolor image. */
1980    @Override
1981    public boolean isTrueColor() {
1982        return isTrueColor;
1983    }
1984
1985    /** @return true if the image interlace is plance interlace. */
1986    @Override
1987    public boolean isPlaneInterlace() {
1988        return isPlaneInterlace;
1989    }
1990
1991    @Override
1992    public void setImage(Image img) {
1993        image = img;
1994        imageComponent.setImage(img);
1995
1996        setImageDirection();
1997    }
1998
1999    private void setImageDirection() {
2000        boolean isHF = isHorizontalFlipped;
2001        boolean isVF = isVerticalFlipped;
2002        int rc = rotateCount;
2003
2004        if (isHF || isVF || rc != 0) {
2005            isHorizontalFlipped = false;
2006            isVerticalFlipped = false;
2007            rotateCount = 0;
2008
2009            if (isHF)
2010                flip(FLIP_HORIZONTAL);
2011
2012            if (isVF)
2013                flip(FLIP_VERTICAL);
2014
2015            while (rc > 0) {
2016                rotate(ROTATE_CW_90);
2017                rc--;
2018            }
2019
2020            while (rc < 0) {
2021                rotate(ROTATE_CCW_90);
2022                rc++;
2023            }
2024
2025        }
2026        else {
2027            if (imageOrigin == Origin.LOWER_LEFT)
2028                flip(FLIP_VERTICAL);
2029            else if (imageOrigin == Origin.UPPER_RIGHT)
2030                flip(FLIP_HORIZONTAL);
2031            if (imageOrigin == Origin.LOWER_RIGHT) {
2032                rotate(ROTATE_CW_90);
2033                rotate(ROTATE_CW_90);
2034            }
2035        }
2036
2037        zoomTo(zoomFactor);
2038    }
2039
2040    @Override
2041    public byte[][] getPalette() {
2042        return imagePalette;
2043    }
2044
2045    @Override
2046    public void setPalette(byte[][] pal) {
2047        imagePalette = pal;
2048        paletteComponent.updatePalette(pal);
2049    }
2050
2051    private void gotoPage(long idx) {
2052        if (dataset.getRank() < 3 || idx == (curFrame - indexBase)) {
2053            return;
2054        }
2055
2056        long[] start = dataset.getStartDims();
2057        int[] selectedIndex = dataset.getSelectedIndex();
2058        long[] dims = dataset.getDims();
2059
2060        if ((idx < 0) || (idx >= dims[selectedIndex[2]])) {
2061            shell.getDisplay().beep();
2062            Tools.showError(shell, "Select", "Frame number must be between " + indexBase + " and "
2063                    + (dims[selectedIndex[2]] - 1 + indexBase));
2064            return;
2065        }
2066
2067        start[selectedIndex[2]] = idx;
2068        curFrame = idx + indexBase;
2069        dataset.clearData();
2070        image = null;
2071        gainBias = null;
2072        imageComponent.setImage(getImage());
2073        frameField.setText(String.valueOf(curFrame));
2074
2075        isHorizontalFlipped = false;
2076        isVerticalFlipped = false;
2077        rotateCount = 0;
2078
2079        if (imageOrigin == Origin.LOWER_LEFT)
2080            flip(FLIP_VERTICAL);
2081        else if (imageOrigin == Origin.UPPER_RIGHT)
2082            flip(FLIP_HORIZONTAL);
2083        if (imageOrigin == Origin.LOWER_RIGHT) {
2084            rotate(ROTATE_CW_90);
2085            rotate(ROTATE_CW_90);
2086        }
2087    }
2088
2089    /**
2090     * Converts a given BufferedImage to ImageData for a SWT-readable Image
2091     *
2092     * @param image The BufferedImage to be converted
2093     *
2094     * @return the image object
2095     */
2096    private org.eclipse.swt.graphics.Image convertBufferedImageToSWTImage(BufferedImage image) {
2097        if (image.getColorModel() instanceof DirectColorModel) {
2098            DirectColorModel colorModel = (DirectColorModel) image.getColorModel();
2099            PaletteData palette = new PaletteData(colorModel.getRedMask(),
2100                    colorModel.getGreenMask(), colorModel.getBlueMask());
2101            ImageData imgData = new ImageData(image.getWidth(),
2102                    image.getHeight(), colorModel.getPixelSize(), palette);
2103
2104            for (int y = 0; y < imgData.height; y++) {
2105                for (int x = 0; x < imgData.width; x++) {
2106                    int rgb = image.getRGB(x, y);
2107                    int pixel = palette.getPixel(new RGB((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF));
2108                    imgData.setPixel(x, y, pixel);
2109                    if (colorModel.hasAlpha()) {
2110                        imgData.setAlpha(x, y, (rgb >> 24) & 0xFF);
2111                    }
2112                }
2113            }
2114
2115            return new org.eclipse.swt.graphics.Image(display, imgData);
2116        }
2117        else if (image.getColorModel() instanceof IndexColorModel) {
2118            IndexColorModel colorModel = (IndexColorModel) image.getColorModel();
2119            int size = colorModel.getMapSize();
2120            byte[] reds = new byte[size];
2121            byte[] greens = new byte[size];
2122            byte[] blues = new byte[size];
2123            colorModel.getReds(reds);
2124            colorModel.getGreens(greens);
2125            colorModel.getBlues(blues);
2126            RGB[] rgbs = new RGB[size];
2127            for (int i = 0; i < rgbs.length; i++) {
2128                rgbs[i] = new RGB(reds[i] & 0xFF, greens[i] & 0xFF, blues[i] & 0xFF);
2129            }
2130            PaletteData palette = new PaletteData(rgbs);
2131            ImageData imgData = new ImageData(image.getWidth(),
2132                    image.getHeight(), colorModel.getPixelSize(), palette);
2133            imgData.transparentPixel = colorModel.getTransparentPixel();
2134            WritableRaster raster = image.getRaster();
2135            int[] pixelArray = new int[1];
2136            for (int y = 0; y < imgData.height; y++) {
2137                for (int x = 0; x < imgData.width; x++) {
2138                    raster.getPixel(x, y, pixelArray);
2139                    imgData.setPixel(x, y, pixelArray[0]);
2140                }
2141            }
2142
2143            return new org.eclipse.swt.graphics.Image(display, imgData);
2144        }
2145        else if (image.getColorModel() instanceof ComponentColorModel) {
2146            ComponentColorModel colorModel = (ComponentColorModel) image.getColorModel();
2147            //ASSUMES: 3 BYTE BGR IMAGE TYPE
2148            PaletteData palette = new PaletteData(0x0000FF, 0x00FF00,0xFF0000);
2149            ImageData imgData = new ImageData(image.getWidth(), image.getHeight(),
2150                    colorModel.getPixelSize(), palette);
2151            //This is valid because we are using a 3-byte Data model with no transparent pixels
2152            imgData.transparentPixel = -1;
2153            WritableRaster raster = image.getRaster();
2154            int[] pixelArray = new int[3];
2155            for (int y = 0; y < imgData.height; y++) {
2156                for (int x = 0; x < imgData.width; x++) {
2157                    raster.getPixel(x, y, pixelArray);
2158                    int pixel = palette.getPixel(new RGB(pixelArray[0], pixelArray[1], pixelArray[2]));
2159                    imgData.setPixel(x, y, pixel);
2160                }
2161            }
2162            return new org.eclipse.swt.graphics.Image(display, imgData);
2163        }
2164
2165        return null;
2166    }
2167
2168    /**
2169     * Creates a RGB indexed image of 256 colors.
2170     *
2171     * @param imageData
2172     *            the byte array of the image data.
2173     * @param palette
2174     *            the color lookup table.
2175     * @param w
2176     *            the width of the image.
2177     * @param h
2178     *            the height of the image.
2179     *
2180     * @return the image.
2181     */
2182    private Image createIndexedImage(byte[] imageData, byte[][] palette, long w, long h)
2183    {
2184        bufferedImage = (BufferedImage)Tools.createIndexedImage(bufferedImage, imageData, palette, w, h);
2185        adjustAlpha(bufferedImage, 0, invalidValueIndex);
2186
2187        return bufferedImage;
2188    }
2189
2190    /**
2191     * Creates a true color image.
2192     *
2193     * The data may be arranged in one of two ways: by pixel or by plane. In
2194     * both cases, the dataset will have a dataspace with three dimensions,
2195     * height, width, and components.
2196     *
2197     * For HDF4, the interlace modes specify orders for the dimensions as:
2198     *
2199     * <pre>
2200     * INTERLACE_PIXEL = [width][height][pixel components]
2201     * INTERLACE_PLANE = [pixel components][width][height]
2202     * </pre>
2203     *
2204     * For HDF5, the interlace modes specify orders for the dimensions as:
2205     *
2206     * <pre>
2207     * INTERLACE_PIXEL = [height][width][pixel components]
2208     * INTERLACE_PLANE = [pixel components][height][width]
2209     * </pre>
2210     *
2211     * @param imageData
2212     *            the byte array of the image data.
2213     * @param planeInterlace
2214     *            flag if the image is plane intelace.
2215     * @param w
2216     *            the width of the image.
2217     * @param h
2218     *            the height of the image.
2219     *
2220     * @return the image.
2221     */
2222    private Image createTrueColorImage(byte[] imageData, boolean planeInterlace, int w, int h)
2223    {
2224        if (bufferedImage == null)
2225            bufferedImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
2226
2227        final int[] pixels = ( (DataBufferInt) bufferedImage.getRaster().getDataBuffer() ).getData();
2228        int len = pixels.length;
2229
2230        int idx = 0, r = 0, g = 0, b = 0;
2231        for (int i = 0; i < h; i++) {
2232            for (int j = 0; j < w; j++) {
2233                if (planeInterlace) {
2234                    r = (imageData[idx] & 0xff)<<16;
2235                    g = (imageData[len + idx] & 0xff)<<8;
2236                    b = (imageData[len * 2 + idx] & 0xff);
2237                }
2238                else {
2239                    r = (imageData[idx * 3] & 0xff)<<16;
2240                    g = (imageData[idx * 3 + 1] & 0xff)<<8;
2241                    b = (imageData[idx * 3 + 2] & 0xff);
2242                }
2243                pixels[idx++] = 0xff000000 | r | g | b;
2244            }
2245        }
2246
2247        adjustAlpha(bufferedImage, 0, invalidValueIndex);
2248
2249        return bufferedImage;
2250    }
2251
2252    private boolean applyImageFilter(ImageFilter filter) {
2253        boolean status = true;
2254        ImageProducer imageProducer = image.getSource();
2255
2256        try {
2257            image = Tools.toBufferedImage(toolkit.createImage(new FilteredImageSource(imageProducer, filter)));
2258            imageComponent.setImage(image);
2259            zoomTo(zoomFactor);
2260        }
2261        catch (Exception err) {
2262            shell.getDisplay().beep();
2263            Tools.showError(shell, "Apply Image Filter", err.getMessage());
2264            status = false;
2265        }
2266
2267        return status;
2268    }
2269
2270    private void applyDataRange(double[] newRange) {
2271        if (doAutoGainContrast && gainBias != null) {
2272            applyAutoGain(gainBiasCurrent, newRange);
2273        }
2274        else {
2275            long w = dataset.getWidth();
2276            long h = dataset.getHeight();
2277
2278            invalidValueIndex.clear(); // data range changed. need to reset
2279
2280            // invalid values
2281            imageByteData = Tools.getBytes(data, newRange, w, h, !dataset.isDefaultImageOrder(),
2282                    dataset.getFilteredImageValues(), true, null, invalidValueIndex);
2283
2284            image = createIndexedImage(imageByteData, imagePalette, w, h);
2285            setImage(image);
2286            zoomTo(zoomFactor);
2287            paletteComponent.updateRange(newRange);
2288        }
2289
2290        dataRange[0] = newRange[0];
2291        dataRange[1] = newRange[1];
2292    }
2293
2294    private void writeSelectionToImage() {
2295        if ((getSelectedArea().width <= 0) || (getSelectedArea().height <= 0)) {
2296            Tools.showError(shell, "Select", "No data to write.\nUse Shift+Mouse_drag to select an image area.");
2297            return;
2298        }
2299
2300        TreeView treeView = viewer.getTreeView();
2301        TreeItem item = treeView.findTreeItem(dataset);
2302        Group pGroup = (Group) item.getParentItem().getData();
2303        HObject root = dataset.getFileFormat().getRootObject();
2304
2305        if (root == null)
2306            return;
2307
2308        ArrayList<HObject> list = new ArrayList<>(dataset.getFileFormat().getNumberOfMembers() + 5);
2309        Iterator<HObject> it = ((Group) root).depthFirstMemberList().iterator();
2310
2311        list.add(dataset.getFileFormat().getRootObject());
2312
2313        while (it.hasNext()) {
2314            list.add(it.next());
2315        }
2316
2317        NewDatasetDialog dialog = new NewDatasetDialog(shell, pGroup, list, this);
2318        dialog.open();
2319
2320        HObject obj = dialog.getObject();
2321        if (obj != null) {
2322            Group pgroup = dialog.getParentGroup();
2323            try {
2324                treeView.addObject(obj, pgroup);
2325            }
2326            catch (Exception ex) {
2327                log.debug("Write selection to image: ", ex);
2328            }
2329        }
2330
2331        list.clear();
2332    }
2333
2334    /** PaletteComponent draws the palette on the side of the image. */
2335    private class PaletteComponent extends Canvas {
2336
2337        private org.eclipse.swt.graphics.Color[] colors = null;
2338        private double[] pixelData = null;
2339        private Dimension paintSize = null;
2340        DecimalFormat format;
2341        double[] dRange = null;
2342
2343        private PaletteComponent(Composite parent, int style, byte[][] palette, double[] range) {
2344            super(parent, style);
2345
2346            paintSize = new Dimension(25, 2);
2347            format = new DecimalFormat("0.00E0");
2348            dRange = range;
2349
2350            if ((palette != null) && (range != null)) {
2351                double ratio = (dRange[1] - dRange[0]) / 255;
2352
2353                pixelData = new double[256];
2354                for (int i = 0; i < 256; i++) {
2355                    pixelData[i] = (dRange[0] + ratio * i);
2356                }
2357            }
2358
2359            updatePalette(palette);
2360
2361            this.addDisposeListener(new DisposeListener() {
2362                @Override
2363                public void widgetDisposed(DisposeEvent e) {
2364                    // Dispose all created colors to prevent memory leak
2365                    for(int i = 0; i < colors.length; i++) {
2366                        if(colors[i] != null) colors[i].dispose();
2367                    }
2368                }
2369            });
2370
2371            this.addPaintListener(new PaintListener() {
2372                @Override
2373                public void paintControl(PaintEvent e) {
2374                    if ((colors == null) && (pixelData == null)) {
2375                        return;
2376                    }
2377
2378                    GC gc = e.gc;
2379                    org.eclipse.swt.graphics.Color oldBackground = gc.getBackground();
2380
2381                    for (int i = 0; i < 256; i++) {
2382                        if ((colors != null) && (colors[i] != null))
2383                            gc.setBackground(colors[i]);
2384                        gc.fillRectangle(0, paintSize.height * i, paintSize.width, paintSize.height);
2385                    }
2386
2387                    FontData[] fontData;
2388                    int fontHeight = 10;
2389
2390                    if (curFont != null) {
2391                        fontData = curFont.getFontData();
2392                    }
2393                    else {
2394                        fontData = Display.getDefault().getSystemFont().getFontData();
2395                    }
2396
2397                    Font newFont = new Font(display, fontData[0].getName(), fontHeight, fontData[0].getStyle());
2398                    gc.setFont(newFont);
2399
2400                    gc.setBackground(oldBackground);
2401                    gc.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_BLACK));
2402
2403                    int trueHeight;
2404                    int i = 0;
2405                    while(i < 25) {
2406                        String str = format.format(pixelData[i * 10]);
2407                        trueHeight = gc.textExtent(str).y;
2408
2409                        gc.drawString(str, paintSize.width + 5, (trueHeight + paintSize.height + 1) * i - ((trueHeight - fontHeight) / 2));
2410
2411                        i++;
2412                    }
2413
2414                    String str = format.format(pixelData[255]);
2415                    trueHeight = gc.textExtent(str).y;
2416
2417                    gc.drawString(str, paintSize.width + 5, (trueHeight + paintSize.height + 1) * i - ((trueHeight - fontHeight) / 2));
2418
2419                    newFont.dispose();
2420                }
2421            });
2422
2423            GridData gridData = new GridData(SWT.FILL, SWT.FILL, false, true);
2424            gridData.widthHint = paintSize.width + 60;
2425            this.setLayoutData(gridData);
2426        }
2427
2428        private void updatePalette(byte[][] palette) {
2429            if ((palette != null) && (dRange != null)) {
2430                colors = new org.eclipse.swt.graphics.Color[256];
2431
2432                int r, g, b;
2433                for (int i = 0; i < 256; i++) {
2434                    r = palette[0][i];
2435                    if (r < 0) {
2436                        r += 256;
2437                    }
2438                    g = palette[1][i];
2439                    if (g < 0) {
2440                        g += 256;
2441                    }
2442                    b = palette[2][i];
2443                    if (b < 0) {
2444                        b += 256;
2445                    }
2446
2447                    colors[i] = new org.eclipse.swt.graphics.Color(display, r, g, b);
2448                }
2449            }
2450
2451            redraw();
2452        }
2453
2454        private void updateRange(double[] newRange) {
2455            if (newRange == null) {
2456                return;
2457            }
2458
2459            dRange = newRange;
2460            double ratio = (dRange[1] - dRange[0]) / 255;
2461            for (int i = 0; i < 256; i++) {
2462                pixelData[i] = (dRange[0] + ratio * i);
2463            }
2464
2465            redraw();
2466        }
2467    }
2468
2469    /** ImageComponent draws the image. */
2470    private class ImageComponent extends Canvas implements ImageObserver
2471    {
2472        /* The BufferedImage is converted to an SWT Image for dislay */
2473        private org.eclipse.swt.graphics.Image convertedImage;
2474
2475        private Dimension originalSize;
2476        private Dimension imageSize;
2477        private Point scrollDim = null;
2478        private Point startPosition; // mouse clicked position
2479        private Point currentPosition; // mouse clicked position
2480        private Rectangle selectedArea;
2481        private Rectangle originalSelectedArea;
2482        private StringBuilder strBuff; // to hold display value
2483        private int yMousePosition = 0; // the vertical position of the current mouse
2484        private ScrollBar hbar = null;
2485        private ScrollBar vbar = null;
2486
2487        public ImageComponent(Composite parent, int style, Image img) {
2488            super(parent, style);
2489
2490            convertedImage = convertBufferedImageToSWTImage((BufferedImage) img);
2491            if (convertedImage != null)
2492                imageSize = new Dimension(convertedImage.getBounds().width, convertedImage.getBounds().height);
2493
2494            originalSize = imageSize;
2495            selectedArea = new Rectangle();
2496            originalSelectedArea = new Rectangle();
2497            setSize(imageSize.width, imageSize.height);
2498            strBuff = new StringBuilder();
2499
2500            this.addDisposeListener(new DisposeListener() {
2501                @Override
2502                public void widgetDisposed(DisposeEvent arg0) {
2503                    if (convertedImage != null && !convertedImage.isDisposed())
2504                        convertedImage.dispose();
2505                }
2506            });
2507
2508            this.addMouseMoveListener(new MouseMoveListener() {
2509                @Override
2510                public void mouseMove(MouseEvent e) {
2511                    currentPosition = new Point(e.x, e.y);
2512
2513                    if((e.stateMask & SWT.BUTTON1) != 0) {
2514                        // If a drag event has occurred, draw a selection Rectangle
2515                        if((e.stateMask & SWT.SHIFT) != 0) {
2516                            int x0 = Math.max(0, Math.min(startPosition.x, currentPosition.x));
2517                            int y0 = Math.max(0, Math.min(startPosition.y, currentPosition.y));
2518                            int x1 = Math.min(imageSize.width, Math.max(startPosition.x, currentPosition.x));
2519                            int y1 = Math.min(imageSize.height, Math.max(startPosition.y, currentPosition.y));
2520
2521                            int w = x1 - x0;
2522                            int h = y1 - y0;
2523
2524                            selectedArea.setBounds(x0, y0, w, h);
2525                            double ratio = 1.0 / zoomFactor;
2526
2527                            originalSelectedArea.setBounds((int) (x0 * ratio),
2528                                    (int) (y0 * ratio), (int) (w * ratio),
2529                                    (int) (h * ratio));
2530                        }
2531                        else {
2532                            if((hbar != null) && hbar.isVisible()) {
2533                                int dx = startPosition.x - currentPosition.x;
2534                                hbar.setSelection(hbar.getSelection() + dx);
2535                            }
2536
2537                            if((vbar != null) && vbar.isVisible()) {
2538                                int dy = startPosition.y - currentPosition.y;
2539                                vbar.setSelection(vbar.getSelection() + dy);
2540                            }
2541                        }
2542
2543                        redraw();
2544                    }
2545
2546                    if(showValues) {
2547                        yMousePosition = e.y;
2548                        showPixelValue(e.x, yMousePosition);
2549                    }
2550                }
2551            });
2552
2553            this.addMouseListener(new MouseListener() {
2554                @Override
2555                public void mouseDoubleClick(MouseEvent e) {
2556                    // Intentional
2557                }
2558
2559                @Override
2560                public void mouseDown(MouseEvent e) {
2561                    startPosition = new Point(e.x, e.y);
2562
2563                    selectedArea.x = startPosition.x; selectedArea.y = startPosition.y;
2564                    selectedArea.width = 0; selectedArea.height = 0;
2565
2566                    scrollDim = imageScroller.getSize();
2567                    hbar = imageScroller.getHorizontalBar();
2568                    vbar = imageScroller.getVerticalBar();
2569
2570                    if((e.stateMask & SWT.SHIFT) != 0) {
2571                        shell.setCursor(display.getSystemCursor(SWT.CURSOR_CROSS));
2572                    }
2573                    else {
2574                        shell.setCursor(display.getSystemCursor(SWT.CURSOR_HAND));
2575                    }
2576                }
2577
2578                @Override
2579                public void mouseUp(MouseEvent e) {
2580                    shell.setCursor(null);
2581
2582                    // Single mouse click
2583                    if(e.count == 1) {
2584                        if(startPosition.x == e.x && startPosition.y == e.y) {
2585                            selectedArea.setBounds(startPosition.x, startPosition.y , 0, 0);
2586                            originalSelectedArea.setBounds(startPosition.x, startPosition.y, 0, 0);
2587                        }
2588
2589                        startPosition = new Point(e.x, e.y);
2590
2591                        if (hbar.isVisible()) {
2592                            hbar.setSelection(startPosition.x - scrollDim.x / 2);
2593                        }
2594
2595                        if (vbar.isVisible()) {
2596                            vbar.setSelection(startPosition.y - scrollDim.y / 2);
2597                        }
2598
2599                        redraw();
2600                    }
2601                }
2602            });
2603
2604            this.addMouseWheelListener(new MouseWheelListener() {
2605                @Override
2606                public void mouseScrolled(MouseEvent e) {
2607                    ScrollBar jb = imageScroller.getVerticalBar();
2608
2609                    jb.getSelection();
2610                    showPixelValue(e.x, yMousePosition);
2611                }
2612            });
2613
2614            this.addPaintListener(new PaintListener() {
2615                @Override
2616                public void paintControl(PaintEvent e) {
2617                    GC gc = e.gc;
2618
2619                    org.eclipse.swt.graphics.Rectangle sourceBounds = convertedImage.getBounds();
2620
2621                    gc.drawImage(convertedImage, 0, 0, sourceBounds.width, sourceBounds.height,
2622                            0, 0, imageSize.width, imageSize.height);
2623
2624                    if ((selectedArea.width > 0) && (selectedArea.height > 0)) {
2625                        gc.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_RED));
2626                        gc.drawRectangle(selectedArea.x, selectedArea.y, selectedArea.width,
2627                                selectedArea.height);
2628                    }
2629                }
2630            });
2631        }
2632
2633        @Override
2634        public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
2635            return false;
2636        }
2637
2638        /**
2639         * Create an image using multiple step bilinear, see details at
2640         * http://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html
2641         *
2642         * @param img          the original image to be scaled
2643         * @param targetWidth  the desired width of the scaled instance
2644         * @param targetHeight the desired height of the scaled instance
2645         * @param highquality  the quality desired
2646         *
2647         * @return a scaled version of the original
2648         */
2649        private Image multiBilinear(Image img, int targetWidth, int targetHeight, boolean highquality) {
2650            Image ret = img;
2651            int w = img.getWidth(null)/2;
2652            int h = img.getHeight(null)/2;
2653
2654            // only do multiple step bilinear for down scale more than two times
2655            if (!highquality || w <=targetWidth || h <=targetHeight)
2656                return ret;
2657
2658            int type = BufferedImage.TYPE_INT_RGB;
2659            if (image instanceof BufferedImage) {
2660                BufferedImage tmp = (BufferedImage)image;
2661                if (tmp.getColorModel().hasAlpha())
2662                    type = BufferedImage.TYPE_INT_ARGB;
2663            }
2664            else {
2665                PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false);
2666                ColorModel cm = pg.getColorModel();
2667                if (cm!=null && cm.hasAlpha())
2668                    type = BufferedImage.TYPE_INT_ARGB;
2669            }
2670
2671            do {
2672                BufferedImage tmp = new BufferedImage(w, h, type);
2673                Graphics2D g2 = tmp.createGraphics();
2674                g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
2675                g2.drawImage(ret, 0, 0, w, h, null);
2676                g2.dispose();
2677                ret = tmp;
2678
2679                w /= 2;
2680                if (w < targetWidth) {
2681                    w = targetWidth;
2682                }
2683
2684                h /= 2;
2685                if (h < targetHeight) {
2686                    h = targetHeight;
2687                }
2688
2689            } while (w != targetWidth || h != targetHeight);
2690
2691            return ret;
2692        }
2693
2694        private void showPixelValue(int x, int y) {
2695            if (!valueField.isVisible() || rotateCount != 0) {
2696                return;
2697            }
2698
2699            if (data == null) {
2700                return;
2701            }
2702
2703            x = (int) (x / zoomFactor);
2704            int w = originalSize.width;
2705
2706            if ((x < 0) || (x >= w)) {
2707                return; // out of image bound
2708            }
2709
2710            y = (int) (y / zoomFactor);
2711            int h = originalSize.height;
2712            if ((y < 0) || (y >= h)) {
2713                return; // out of image bound
2714            }
2715
2716            // transfer location to the original coordinator
2717            if (isHorizontalFlipped) {
2718                x = w - 1 - x;
2719            }
2720
2721            if (isVerticalFlipped) {
2722                y = h - 1 - y;
2723            }
2724
2725            strBuff.setLength(0); // reset the string buffer
2726            strBuff.append("x=")
2727                   .append(x + indexBase)
2728                   .append(",   y=")
2729                   .append(y + indexBase)
2730                   .append(",   value=");
2731
2732            if (isTrueColor) {
2733                int i0, i1, i2;
2734                String r, g, b;
2735
2736                if (isPlaneInterlace) {
2737                    i0 = y * w + x; // index for the first plane
2738                    i1 = i0 + w * h; // index for the second plane
2739                    i2 = i0 + 2 * w * h; // index for the third plane
2740                }
2741                else {
2742                    i0 = 3 * (y * w + x); // index for the first pixel
2743                    i1 = i0 + 1; // index for the second pixel
2744                    i2 = i0 + 2; // index for the third pixel
2745                }
2746
2747                if (dataset.getDatatype().isUnsigned() && !isUnsignedConverted) {
2748                    r = String.valueOf(convertUnsignedPoint(i0));
2749                    g = String.valueOf(convertUnsignedPoint(i1));
2750                    b = String.valueOf(convertUnsignedPoint(i2));
2751                }
2752                else {
2753                    r = String.valueOf(Array.get(data, i0));
2754                    g = String.valueOf(Array.get(data, i1));
2755                    b = String.valueOf(Array.get(data, i2));
2756                }
2757
2758                strBuff.append("(").append(r + ", " + g + ", " + b).append(")");
2759            } // (isTrueColor)
2760            else {
2761                int idx;
2762
2763                if (!dataset.isDefaultImageOrder())
2764                    idx = x * h + y;
2765                else
2766                    idx = y * w + x;
2767
2768                if (dataset.getDatatype().isUnsigned() && !isUnsignedConverted) {
2769                    strBuff.append(convertUnsignedPoint(idx));
2770                }
2771                else {
2772                    strBuff.append(Array.get(data, idx));
2773                }
2774            }
2775
2776            valueField.setText(strBuff.toString());
2777        } // private void showPixelValue
2778
2779        private long convertUnsignedPoint(int idx) {
2780            long l = 0;
2781
2782            if (NT == 'B') {
2783                byte b = Array.getByte(data, idx);
2784
2785                if (b < 0) {
2786                    l = (long) b + 256;
2787                }
2788                else {
2789                    l = b;
2790                }
2791            }
2792            else if (NT == 'S') {
2793                short s = Array.getShort(data, idx);
2794                if (s < 0) {
2795                    l = (long) s + 65536;
2796                }
2797                else {
2798                    l = s;
2799                }
2800            }
2801            else if (NT == 'I') {
2802                int i = Array.getInt(data, idx);
2803                if (i < 0) {
2804                    l = i + 4294967296L;
2805                }
2806                else {
2807                    l = i;
2808                }
2809            }
2810
2811            return l;
2812        }
2813
2814        private void selectAll() {
2815            selectedArea.setBounds(0, 0, imageSize.width, imageSize.height);
2816            originalSelectedArea.setBounds(0, 0, originalSize.width, originalSize.height);
2817
2818            redraw();
2819        }
2820
2821        private void setImageSize(Dimension size) {
2822            imageSize = size;
2823            setSize(imageSize.width, imageSize.height);
2824
2825            int w = selectedArea.width;
2826            int h = selectedArea.height;
2827            if ((w > 0) && (h > 0)) {
2828                // use fixed selected area to reduce the rounding error
2829                selectedArea.setBounds(
2830                        (int) (originalSelectedArea.x * zoomFactor),
2831                        (int) (originalSelectedArea.y * zoomFactor),
2832                        (int) (originalSelectedArea.width * zoomFactor),
2833                        (int) (originalSelectedArea.height * zoomFactor));
2834            }
2835
2836            redraw();
2837        }
2838
2839        private void setImage(Image img) {
2840            /* Make sure to dispose the old image first so resources aren't leaked */
2841            if (convertedImage != null && !convertedImage.isDisposed())
2842                convertedImage.dispose();
2843
2844            convertedImage = convertBufferedImageToSWTImage((BufferedImage) img);
2845            if (convertedImage != null)
2846                imageSize = new Dimension(convertedImage.getBounds().width, convertedImage.getBounds().height);
2847            originalSize = imageSize;
2848            selectedArea.width = 0;
2849            selectedArea.height = 0;
2850            setSize(imageSize.width, imageSize.height);
2851
2852            setImageSize(new Dimension((int) (originalSize.width * zoomFactor),
2853                    (int) (originalSize.height * zoomFactor)));
2854
2855            redraw();
2856        }
2857    }
2858
2859    /**
2860     * FlipFilter creates image filter to flip image horizontally or vertically.
2861     */
2862    public static class FlipFilter extends ImageFilter
2863    {
2864        /** flip direction */
2865        private int direction;
2866
2867        /** pixel value */
2868        private int[] raster = null;
2869
2870        /** width & height */
2871        private int imageWidth;
2872        private int imageHeight;
2873
2874        /**
2875         * Constructs an image filter to flip horizontally or vertically.
2876         *
2877         * @param d
2878         *            the flip direction.
2879         */
2880        public FlipFilter(int d) {
2881            if (d < FLIP_HORIZONTAL)
2882                d = FLIP_HORIZONTAL;
2883            else if (d > FLIP_VERTICAL)
2884                d = FLIP_VERTICAL;
2885
2886            direction = d;
2887        }
2888
2889        @Override
2890        public void setDimensions(int w, int h) {
2891            imageWidth = w;
2892            imageHeight = h;
2893
2894            // specify the raster
2895            if (raster == null)
2896                raster = new int[imageWidth * imageHeight];
2897
2898            consumer.setDimensions(imageWidth, imageHeight);
2899        }
2900
2901        @Override
2902        public void setPixels(int x, int y, int w, int h, ColorModel model,
2903                byte[] pixels, int off, int scansize) {
2904            int srcoff = off;
2905            int dstoff = y * imageWidth + x;
2906            for (int yc = 0; yc < h; yc++) {
2907                for (int xc = 0; xc < w; xc++)
2908                    raster[dstoff++] = model.getRGB(pixels[srcoff++] & 0xff);
2909
2910                srcoff += (scansize - w);
2911                dstoff += (imageWidth - w);
2912            }
2913        }
2914
2915        @Override
2916        public void setPixels(int x, int y, int w, int h, ColorModel model,
2917                int[] pixels, int off, int scansize) {
2918            int srcoff = off;
2919            int dstoff = y * imageWidth + x;
2920
2921            for (int yc = 0; yc < h; yc++) {
2922                for (int xc = 0; xc < w; xc++)
2923                    raster[dstoff++] = model.getRGB(pixels[srcoff++]);
2924                srcoff += (scansize - w);
2925                dstoff += (imageWidth - w);
2926            }
2927        }
2928
2929        @Override
2930        public void imageComplete(int status) {
2931            if ((status == IMAGEERROR) || (status == IMAGEABORTED)) {
2932                consumer.imageComplete(status);
2933                return;
2934            }
2935
2936            int[] pixels = new int[imageWidth];
2937            for (int y = 0; y < imageHeight; y++) {
2938                if (direction == FLIP_VERTICAL) {
2939                    // grab pixel values of the target line ...
2940                    int pos = (imageHeight - 1 - y) * imageWidth;
2941                    for (int kk = 0; kk < imageWidth; kk++)
2942                        pixels[kk] = raster[pos + kk];
2943                }
2944                else {
2945                    int pos = y * imageWidth;
2946                    for (int kk = 0; kk < imageWidth; kk++)
2947                        pixels[kk] = raster[pos + kk];
2948
2949                    // swap the pixel values of the target line
2950                    int hw = imageWidth / 2;
2951                    for (int kk = 0; kk < hw; kk++) {
2952                        int tmp = pixels[kk];
2953                        pixels[kk] = pixels[imageWidth - kk - 1];
2954                        pixels[imageWidth - kk - 1] = tmp;
2955                    }
2956                }
2957
2958                // consumer it ....
2959                consumer.setPixels(0, y, imageWidth, 1,
2960                        ColorModel.getRGBdefault(), pixels, 0, imageWidth);
2961            } // (int y = 0; y < imageHeight; y++)
2962
2963            // complete ?
2964            consumer.imageComplete(status);
2965        }
2966    } // private class FlipFilter extends ImageFilter
2967
2968    /**
2969     * Apply general brightness/contrast algorithm. For details, visit
2970     * http://www.developerfusion.co.uk/
2971     *
2972     * The general algorithm is represented by: If Brighten = True New_Value =
2973     * Old_Value + Adjustment_Amount Else New_Value = Old_Value -
2974     * Adjustment_Amount If New_Value < Value_Minimum New_Value = Value_Minimum
2975     * If New_Value > Value_Maximum New_Value = Value_Maximum
2976     *
2977     * Contrast is a complicated operation. It is hard to formulate a
2978     * "general algorithm". Here is the closest representation
2979     * (Contrast_Value=[0, 2]):
2980     *
2981     * //Converts to a percent //[0, 1] New_Value = Old_Value / 255
2982     *
2983     * //Centers on 0 instead of .5 //[-.5, .5] New_Value -= 0.5
2984     *
2985     * //Adjusts by Contrast_Value //[-127.5, 127.5], usually [-1, 1] New_Value
2986     * *= Contrast_Value
2987     *
2988     * //Re-add .5 (un-center over 0) //[-127, 128] New_Value += 0.5
2989     *
2990     * //Re-multiply by 255 (un-convert to percent) //[-32385, 32640], usually
2991     * [0, 255] New_Value *= 255 //Clamp [0, 255] If(New_Value > 255) New_Value
2992     * = 255 If(New_Value < 0) New_Value = 0
2993     */
2994    private class BrightnessFilter extends RGBImageFilter
2995    {
2996        // brightness level = [-200, 200]
2997        int brightLevel = 0;
2998
2999        // contrast level [0, 4]
3000        float contrastLevel = 0;
3001
3002        public BrightnessFilter(int blevel, int clevel) {
3003            if (blevel < -100)
3004                brightLevel = -100;
3005            else if (blevel > 100)
3006                brightLevel = 100;
3007            else
3008                brightLevel = blevel;
3009            brightLevel *= 2;
3010
3011            if (clevel < -100)
3012                clevel = -100;
3013            else if (clevel > 100)
3014                clevel = 100;
3015
3016            if (clevel > 0)
3017                contrastLevel = (clevel / 100f + 1) * 2;
3018            else if (clevel < 0)
3019                contrastLevel = (clevel / 100f + 1) / 2;
3020            else
3021                contrastLevel = 0;
3022
3023            canFilterIndexColorModel = true;
3024        }
3025
3026        @Override
3027        public int filterRGB(int x, int y, int rgb) {
3028            // adjust brightness first, then adjust contrast
3029            // it gives more color depth
3030
3031            if (brightLevel != 0) {
3032                int r = (rgb & 0x00ff0000) >> 16;
3033                int g = (rgb & 0x0000ff00) >> 8;
3034                int b = (rgb & 0x000000ff);
3035
3036                r += brightLevel;
3037                g += brightLevel;
3038                b += brightLevel;
3039
3040                if (r < 0)
3041                    r = 0;
3042                if (r > 255)
3043                    r = 255;
3044                if (g < 0)
3045                    g = 0;
3046                if (g > 255)
3047                    g = 255;
3048                if (b < 0)
3049                    b = 0;
3050                if (b > 255)
3051                    b = 255;
3052
3053                r = (r << 16) & 0x00ff0000;
3054                g = (g << 8) & 0x0000ff00;
3055                b = b & 0x000000ff;
3056
3057                rgb = ((rgb & 0xff000000) | r | g | b);
3058            }
3059
3060            // do not compare float using !=0 or ==0
3061            if (contrastLevel > 0.000001) {
3062                int r = (rgb & 0x00ff0000) >> 16;
3063                    int g = (rgb & 0x0000ff00) >> 8;
3064                    int b = (rgb & 0x000000ff);
3065
3066                    float f = r / 255f;
3067                    f -= 0.5;
3068                    f *= contrastLevel;
3069                    f += 0.5;
3070                    f *= 255f;
3071                    if (f < 0)
3072                        f = 0;
3073                    if (f > 255)
3074                        f = 255;
3075                    r = (int) f;
3076
3077                    f = g / 255f;
3078                    f -= 0.5;
3079                    f *= contrastLevel;
3080                    f += 0.5;
3081                    f *= 255f;
3082                    if (f < 0)
3083                        f = 0;
3084                    if (f > 255)
3085                        f = 255;
3086                    g = (int) f;
3087
3088                    f = b / 255f;
3089                    f -= 0.5;
3090                    f *= contrastLevel;
3091                    f += 0.5;
3092                    f *= 255f;
3093                    if (f < 0)
3094                        f = 0;
3095                    if (f > 255)
3096                        f = 255;
3097                    b = (int) f;
3098
3099                    r = (r << 16) & 0x00ff0000;
3100                    g = (g << 8) & 0x0000ff00;
3101                    b = b & 0x000000ff;
3102
3103                    rgb = ((rgb & 0xff000000) | r | g | b);
3104            }
3105
3106            return rgb;
3107        }
3108    }
3109
3110    /**
3111     * Makes an image filter for contour.
3112     */
3113    private class ContourFilter extends ImageFilter
3114    {
3115        // default color model
3116        private ColorModel defaultRGB;
3117
3118        // contour level
3119        int level;
3120
3121        // the table of the contour levels
3122        int[] levels;
3123
3124        // colors for drawable contour line
3125        int[] levelColors;
3126
3127        // default RGB
3128
3129        // pixel value
3130        private int[] raster = null;
3131
3132        // width & height
3133        private int imageWidth;
3134        private int imageHeight;
3135
3136        /**
3137         * Create an contour filter for a given level contouring.
3138         *
3139         * @param theLevel
3140         *            the contour level.
3141         */
3142        private ContourFilter(int theLevel) {
3143            defaultRGB = ColorModel.getRGBdefault();
3144
3145            levelColors = new int[9];
3146            levelColors[0] = Color.red.getRGB();
3147            levelColors[1] = Color.green.getRGB();
3148            levelColors[2] = Color.blue.getRGB();
3149            levelColors[3] = Color.magenta.getRGB();
3150            levelColors[4] = Color.orange.getRGB();
3151            levelColors[5] = Color.cyan.getRGB();
3152            levelColors[6] = Color.black.getRGB();
3153            levelColors[7] = Color.pink.getRGB();
3154            levelColors[8] = Color.yellow.getRGB();
3155
3156            if (theLevel < 1)
3157                theLevel = 1;
3158            else if (theLevel > 9)
3159                theLevel = 9;
3160
3161            level = theLevel;
3162            levels = new int[level];
3163
3164            int dx = 128 / level;
3165            for (int i = 0; i < level; i++)
3166                levels[i] = (i + 1) * dx;
3167        }
3168
3169        @Override
3170        public void setDimensions(int width, int height) {
3171            this.imageWidth = width;
3172            this.imageHeight = height;
3173
3174            // specify the raster
3175            if (raster == null)
3176                raster = new int[imageWidth * imageHeight];
3177
3178            consumer.setDimensions(width, height);
3179        }
3180
3181        @Override
3182        public void setPixels(int x, int y, int w, int h, ColorModel model,
3183                byte[] pixels, int off, int scansize) {
3184            int rgb = 0;
3185            int srcoff = off;
3186            int dstoff = y * imageWidth + x;
3187
3188            for (int yc = 0; yc < h; yc++) {
3189                for (int xc = 0; xc < w; xc++) {
3190                    rgb = model.getRGB(pixels[srcoff++] & 0xff);
3191                    raster[dstoff++] = (((rgb >> 16) & 0xff)
3192                            + ((rgb >> 8) & 0xff) + (rgb & 0xff)) / 3;
3193                }
3194                srcoff += (scansize - w);
3195                dstoff += (imageWidth - w);
3196            }
3197
3198        }
3199
3200        @Override
3201        public void setPixels(int x, int y, int w, int h, ColorModel model,
3202                int[] pixels, int off, int scansize) {
3203            int rgb = 0;
3204            int srcoff = off;
3205            int dstoff = y * imageWidth + x;
3206
3207            for (int yc = 0; yc < h; yc++) {
3208                for (int xc = 0; xc < w; xc++) {
3209                    rgb = model.getRGB(pixels[srcoff++] & 0xff);
3210                    raster[dstoff++] = (((rgb >> 16) & 0xff)
3211                            + ((rgb >> 8) & 0xff) + (rgb & 0xff)) / 3;
3212                }
3213
3214                srcoff += (scansize - w);
3215                dstoff += (imageWidth - w);
3216            }
3217        }
3218
3219        @Override
3220        public void imageComplete(int status) {
3221            if ((status == IMAGEERROR) || (status == IMAGEABORTED)) {
3222                consumer.imageComplete(status);
3223                return;
3224            }
3225
3226            int[] pixels = new int[imageWidth * imageHeight];
3227            for (int z = 0; z < levels.length; z++) {
3228                int currentLevel = levels[z];
3229                int color = levelColors[z];
3230
3231                setContourLine(raster, pixels, currentLevel, color, imageWidth, imageHeight);
3232            }
3233
3234            int[] line = new int[imageWidth];
3235            for (int y = 0; y < imageHeight; y++) {
3236                for (int x = 0; x < imageWidth; x++)
3237                    line[x] = pixels[y * imageWidth + x];
3238
3239                consumer.setPixels(0, y, imageWidth, 1, defaultRGB, line, 0, imageWidth);
3240            } // (int y = 0; y < imageHeight; y++)
3241
3242            // complete ?
3243            consumer.imageComplete(status);
3244        }
3245
3246        /**
3247         * draw a contour line based on the current parameter---level, color
3248         *
3249         * @param raster
3250         *            the data of the raster image.
3251         * @param pixels
3252         *            the pixel value of the image.
3253         * @param level
3254         *            the contour level.
3255         * @param color
3256         *            the color of the contour line.
3257         * @param w
3258         *            the width of the image.
3259         * @param h
3260         *            the height of the image.
3261         */
3262        private void setContourLine(int[] raster, int[] pixels, int level, int color, int w, int h) {
3263            int p = 0; // entrance point
3264            int q = p + (w * h - 1); // bottom right point
3265            int u = 0 + (w - 1); // top right point
3266
3267            // first round
3268            while (true) {
3269                while (p < u) {
3270                    int rgb = raster[p];
3271                    if (rgb < level) {
3272                        while ((raster[p] < level) && (p < u))
3273                            p++;
3274                        if (raster[p] >= level)
3275                            pixels[p] = color;
3276                    }
3277                    else if (rgb == level) {
3278                        while ((raster[p] == level) && (p < u))
3279                            p++;
3280                        if ((raster[p] < level) || (raster[p] > level))
3281                            pixels[p] = color;
3282                    }
3283                    else {
3284                        while ((raster[p] > level) && (p < u))
3285                            p++;
3286                        if ((raster[p] <= level))
3287                            pixels[p] = color;
3288                    }
3289                }
3290
3291                if (u == q)
3292                    break;
3293                else {
3294                    u += w;
3295                    p++;
3296                }
3297            }
3298        }
3299
3300    } // private class ContourFilter extends ImageFilter
3301
3302
3303    /**
3304     * Makes an image filter for rotating image by 90 degrees.
3305     */
3306    public static class Rotate90Filter extends ImageFilter
3307    {
3308        private ColorModel defaultRGB = ColorModel.getRGBdefault();
3309
3310        private double[] coord = new double[2];
3311
3312        private int[] raster;
3313        private int xoffset;
3314        private int yoffset;
3315        private int srcW;
3316        private int srcH;
3317        private int dstW;
3318        private int dstH;
3319        private int direction;
3320
3321        /**
3322         * Image filter for rotating image by 90 degrees.
3323         *
3324         * @param dir
3325         *        the direction to rotate the image
3326         *        ROTATE_CW_90 or ROTATE_CCW_90
3327         */
3328        public Rotate90Filter(int dir) {
3329            direction = dir;
3330        }
3331
3332        /**
3333         * Transform when rotating image by 90 degrees.
3334         *
3335         * @param x
3336         *        the x coordinate to transform
3337         * @param y
3338         *        the y coordinate to transform
3339         * @param retcoord
3340         *        the x.y coordinate transformed
3341         */
3342        public void transform(double x, double y, double[] retcoord) {
3343            if (direction == ROTATE_CW_90) {
3344                retcoord[0] = -y;
3345                retcoord[1] = x;
3346            }
3347            else {
3348                retcoord[0] = y;
3349                retcoord[1] = -x;
3350            }
3351        }
3352
3353        /**
3354         * Transform when rotating image by 90 degrees.
3355         *
3356         * @param x
3357         *        the x coordinate to transform
3358         * @param y
3359         *        the y coordinate to transform
3360         * @param retcoord
3361         *        the x.y coordinate transformed
3362         */
3363        public void itransform(double x, double y, double[] retcoord) {
3364            if (direction == ROTATE_CCW_90) {
3365                retcoord[0] = -y;
3366                retcoord[1] = x;
3367            }
3368            else {
3369                retcoord[0] = y;
3370                retcoord[1] = -x;
3371            }
3372        }
3373
3374        /**
3375         * Transform the image specified by a rectangle.
3376         *
3377         * @param rect
3378         *        the rectangle coordinates transformed
3379         */
3380        public void transformBBox(Rectangle rect) {
3381            double minx = Double.POSITIVE_INFINITY;
3382            double miny = Double.POSITIVE_INFINITY;
3383            double maxx = Double.NEGATIVE_INFINITY;
3384            double maxy = Double.NEGATIVE_INFINITY;
3385            for (int y = 0; y <= 1; y++) {
3386                for (int x = 0; x <= 1; x++) {
3387                    transform((double) rect.x + x * rect.width, (double) rect.y + y * rect.height, coord);
3388                    minx = Math.min(minx, coord[0]);
3389                    miny = Math.min(miny, coord[1]);
3390                    maxx = Math.max(maxx, coord[0]);
3391                    maxy = Math.max(maxy, coord[1]);
3392                }
3393            }
3394            rect.x = (int) Math.floor(minx);
3395            rect.y = (int) Math.floor(miny);
3396            rect.width = (int) Math.ceil(maxx) - rect.x;
3397            rect.height = (int) Math.ceil(maxy) - rect.y;
3398        }
3399
3400        @Override
3401        public void setDimensions(int width, int height) {
3402            Rectangle rect = new Rectangle(0, 0, width, height);
3403            transformBBox(rect);
3404            xoffset = -rect.x;
3405            yoffset = -rect.y;
3406            srcW = width;
3407            srcH = height;
3408            dstW = rect.width;
3409            dstH = rect.height;
3410            raster = new int[srcW * srcH];
3411            consumer.setDimensions(dstW, dstH);
3412        }
3413
3414        @Override
3415        public void setProperties(Hashtable props) {
3416            props = (Hashtable) props.clone();
3417            Object o = props.get("filters");
3418            if (o == null)
3419                props.put("filters", toString());
3420            else if (o instanceof String)
3421                props.put("filters", ((String) o) + toString());
3422            consumer.setProperties(props);
3423        }
3424
3425        @Override
3426        public void setColorModel(ColorModel model) {
3427            consumer.setColorModel(defaultRGB);
3428        }
3429
3430        @Override
3431        public void setHints(int hintflags) {
3432            consumer.setHints(TOPDOWNLEFTRIGHT | COMPLETESCANLINES | SINGLEPASS
3433                    | (hintflags & SINGLEFRAME));
3434        }
3435
3436        @Override
3437        public void setPixels(int x, int y, int w, int h, ColorModel model,
3438                byte[] pixels, int off, int scansize) {
3439            int srcoff = off;
3440            int dstoff = y * srcW + x;
3441            for (int yc = 0; yc < h; yc++) {
3442                for (int xc = 0; xc < w; xc++)
3443                    raster[dstoff++] = model.getRGB(pixels[srcoff++] & 0xff);
3444                srcoff += (scansize - w);
3445                dstoff += (srcW - w);
3446            }
3447        }
3448
3449        @Override
3450        public void setPixels(int x, int y, int w, int h, ColorModel model,
3451                int[] pixels, int off, int scansize) {
3452            int srcoff = off;
3453            int dstoff = y * srcW + x;
3454            if (model == defaultRGB) {
3455                for (int yc = 0; yc < h; yc++) {
3456                    System.arraycopy(pixels, srcoff, raster, dstoff, w);
3457                    srcoff += scansize;
3458                    dstoff += srcW;
3459                }
3460            }
3461            else {
3462                for (int yc = 0; yc < h; yc++) {
3463                    for (int xc = 0; xc < w; xc++)
3464                        raster[dstoff++] = model.getRGB(pixels[srcoff++]);
3465                    srcoff += (scansize - w);
3466                    dstoff += (srcW - w);
3467                }
3468            }
3469        }
3470
3471        @Override
3472        public void imageComplete(int status) {
3473            if ((status == IMAGEERROR) || (status == IMAGEABORTED)) {
3474                consumer.imageComplete(status);
3475                return;
3476            }
3477            int[] pixels = new int[dstW];
3478            for (int dy = 0; dy < dstH; dy++) {
3479                itransform(0 - (double) xoffset, dy - (double) yoffset, coord);
3480                double x1 = coord[0];
3481                double y1 = coord[1];
3482                itransform(dstW - (double) xoffset, dy - (double) yoffset, coord);
3483                double x2 = coord[0];
3484                double y2 = coord[1];
3485                double xinc = (x2 - x1) / dstW;
3486                double yinc = (y2 - y1) / dstW;
3487                for (int dx = 0; dx < dstW; dx++) {
3488                    int sx = (int) Math.round(x1);
3489                    int sy = (int) Math.round(y1);
3490                    if ((sx < 0) || (sy < 0) || (sx >= srcW) || (sy >= srcH))
3491                        pixels[dx] = 0;
3492                    else
3493                        pixels[dx] = raster[sy * srcW + sx];
3494                    x1 += xinc;
3495                    y1 += yinc;
3496                }
3497                consumer.setPixels(0, dy, dstW, 1, defaultRGB, pixels, 0, dstW);
3498            }
3499            consumer.imageComplete(status);
3500        }
3501    } // private class RotateFilter
3502
3503    /**
3504     * Makes animation for 3D images.
3505     */
3506    private class Animation extends Dialog
3507    {
3508        private static final int MAX_ANIMATION_IMAGE_SIZE = 300;
3509
3510        /* A list of frames to display for animation */
3511        private org.eclipse.swt.graphics.Image[] frames = null;
3512
3513        private Shell shell;
3514        private Canvas canvas; // Canvas to draw the image
3515        private int numberOfImages = 0;
3516        private int currentFrame = 0;
3517        private int sleepTime = 200;
3518
3519        public Animation(Shell parent, int style, ScalarDS dataset) {
3520            super(parent, style);
3521
3522            long[] dims = dataset.getDims();
3523            long[] stride = dataset.getStride();
3524            long[] start = dataset.getStartDims();
3525            long[] selected = dataset.getSelectedDims();
3526            int[] selectedIndex = dataset.getSelectedIndex();
3527            int rank = dataset.getRank();
3528            if (animationSpeed != 0)
3529                sleepTime = 1000 / animationSpeed;
3530
3531            // back up the start and selected size
3532            long[] tstart = new long[rank];
3533            long[] tselected = new long[rank];
3534            long[] tstride = new long[rank];
3535            System.arraycopy(start, 0, tstart, 0, rank);
3536            System.arraycopy(selected, 0, tselected, 0, rank);
3537            System.arraycopy(stride, 0, tstride, 0, rank);
3538
3539            int strideN = 1;
3540            int maxSize = (int) Math.max(selected[selectedIndex[0]], selected[selectedIndex[1]]);
3541            if (maxSize > MAX_ANIMATION_IMAGE_SIZE)
3542                strideN = (int) ((double) maxSize / (double) MAX_ANIMATION_IMAGE_SIZE + 0.5);
3543
3544            start[selectedIndex[0]] = 0;
3545            start[selectedIndex[1]] = 0;
3546            start[selectedIndex[2]] = 0;
3547            selected[selectedIndex[0]] = dims[selectedIndex[0]] / strideN;
3548            selected[selectedIndex[1]] = dims[selectedIndex[1]] / strideN;
3549            selected[selectedIndex[2]] = 1;
3550            stride[selectedIndex[0]] = strideN;
3551            stride[selectedIndex[1]] = strideN;
3552            stride[selectedIndex[2]] = 1;
3553
3554            Object data3d = null;
3555            byte[] byteData = null;
3556            int h = (int) selected[selectedIndex[0]];
3557            int w = (int) selected[selectedIndex[1]];
3558            int size = w * h;
3559
3560            numberOfImages = (int) dims[selectedIndex[2]];
3561            frames = new org.eclipse.swt.graphics.Image[numberOfImages];
3562
3563            BufferedImage frameImage;
3564            try {
3565                for (int i = 0; i < numberOfImages; i++) {
3566                    start[selectedIndex[2]] = i;
3567
3568                    dataset.clearData();
3569                    try {
3570                        data3d = dataset.read();
3571                    }
3572                    catch (Exception err) {
3573                        continue;
3574                    }
3575
3576                    byteData = new byte[size];
3577
3578                    byteData = Tools.getBytes(data3d, dataRange, w, h, false, dataset.getFilteredImageValues(),
3579                            true, byteData);
3580
3581                    frameImage = (BufferedImage) createIndexedImage(byteData, imagePalette, w, h);
3582                    frames[i] = convertBufferedImageToSWTImage(frameImage);
3583                }
3584            }
3585            finally {
3586                // set back to original state
3587                System.arraycopy(tstart, 0, start, 0, rank);
3588                System.arraycopy(tselected, 0, selected, 0, rank);
3589                System.arraycopy(tstride, 0, stride, 0, rank);
3590            }
3591        }
3592
3593        public void open() {
3594            Shell parent = getParent();
3595            shell = new Shell(parent, SWT.SHELL_TRIM | SWT.APPLICATION_MODAL);
3596            shell.setFont(curFont);
3597            shell.setText("Animation - " + dataset.getName());
3598            shell.setImage(ViewProperties.getHdfIcon());
3599            shell.setLayout(new GridLayout(1, true));
3600
3601            canvas = new Canvas(shell, SWT.DOUBLE_BUFFERED);
3602            canvas.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
3603            canvas.addPaintListener(new PaintListener() {
3604                @Override
3605                public void paintControl(PaintEvent e) {
3606                    GC gc = e.gc;
3607
3608                    if (frames == null)
3609                        return;
3610
3611                    org.eclipse.swt.graphics.Rectangle canvasBounds = canvas.getBounds();
3612                    int x = ((canvasBounds.width / 2) - (frames[currentFrame].getBounds().width / 2));
3613                    int y = ((canvasBounds.height / 2) - (frames[currentFrame].getBounds().height / 2));
3614                    gc.drawImage(frames[currentFrame], x, y);
3615
3616                    gc.dispose();
3617                }
3618            });
3619
3620            canvas.addDisposeListener(new DisposeListener() {
3621                @Override
3622                public void widgetDisposed(DisposeEvent arg0) {
3623                    /* Make sure to dispose of all generated images */
3624                    for (int i = 0; i < frames.length; i++) {
3625                        if (frames[i] != null && !frames[i].isDisposed())
3626                            frames[i].dispose();
3627                    }
3628                }
3629            });
3630
3631            Button closeButton = new Button(shell, SWT.PUSH);
3632            closeButton.setFont(curFont);
3633            closeButton.setText("&Close");
3634            closeButton.setLayoutData(new GridData(SWT.CENTER, SWT.FILL, false, false));
3635            closeButton.addSelectionListener(new SelectionAdapter() {
3636                @Override
3637                public void widgetSelected(SelectionEvent e) {
3638                    shell.dispose();
3639                }
3640            });
3641
3642            shell.pack();
3643
3644            shell.setSize(MAX_ANIMATION_IMAGE_SIZE, MAX_ANIMATION_IMAGE_SIZE);
3645
3646            org.eclipse.swt.graphics.Rectangle parentBounds = parent.getBounds();
3647            Point shellSize = shell.getSize();
3648            shell.setLocation((parentBounds.x + (parentBounds.width / 2)) - (shellSize.x / 2),
3649                    (parentBounds.y + (parentBounds.height / 2)) - (shellSize.y / 2));
3650
3651            shell.open();
3652
3653            Runnable runnable = new AnimationThread();
3654
3655            /**
3656             * Run the animation. This method is called by class Thread.
3657             *
3658             * @see java.lang.Thread
3659             */
3660            Display.getDefault().timerExec(sleepTime, runnable);
3661
3662            Display openDisplay = parent.getDisplay();
3663            while(!shell.isDisposed()) {
3664                if (!openDisplay.readAndDispatch())
3665                    openDisplay.sleep();
3666            }
3667
3668            openDisplay.timerExec(-1, runnable);
3669        }
3670
3671        private class AnimationThread implements Runnable
3672        {
3673            @Override
3674            public void run() {
3675                if ((frames == null) || (canvas == null))
3676                    return;
3677
3678                if (++currentFrame >= numberOfImages)
3679                    currentFrame = 0;
3680
3681                canvas.redraw();
3682
3683                Display.getCurrent().timerExec(sleepTime, this);
3684            }
3685        }
3686    }
3687
3688    private class DataRangeDialog extends Dialog
3689    {
3690        private Shell shell;
3691        private Slider minSlider;
3692        private Slider maxSlider;
3693        private Text minField;
3694        private Text maxField;
3695        final int nTICKS = 10;
3696        double tickRatio = 1;
3697        final int rangeW = 500;
3698        final int rangeH = 400;
3699        double[] rangeMinMaxCurrent = { 0, 0 };
3700        double min;
3701        double max;
3702        double minOrg;
3703        double maxOrg;
3704        final double[] minmaxPrevious = { 0, 0 };
3705        final double[] minmaxDist = { 0, 0 };
3706
3707        final DecimalFormat numberFormat = new DecimalFormat("#.##E0");
3708
3709        public DataRangeDialog(Shell parent, int style, double[] minmaxCurrent,
3710                double[] minmaxOriginal, final int[] dataDist) {
3711
3712            super(parent, style);
3713
3714            Tools.findMinMax(dataDist, minmaxDist, null);
3715
3716            if ((minmaxOriginal == null) || (minmaxOriginal.length <= 1)) {
3717                minmaxCurrent[0] = 0;
3718                minmaxCurrent[1] = 255;
3719            }
3720            else {
3721                if (minmaxOriginal[0] == minmaxOriginal[1])
3722                    Tools.findMinMax(data, minmaxOriginal, dataset.getFillValue());
3723
3724                minmaxCurrent[0] = minmaxOriginal[0];
3725                minmaxCurrent[1] = minmaxOriginal[1];
3726            }
3727
3728            minmaxPrevious[0] = min = minmaxCurrent[0];
3729            minmaxPrevious[1] = max = minmaxCurrent[1];
3730            minOrg = originalRange[0];
3731            maxOrg = originalRange[1];
3732
3733            tickRatio = (maxOrg - minOrg) / nTICKS;
3734        }
3735
3736        public void open() {
3737            Shell parent = getParent();
3738            shell = new Shell(parent, SWT.SHELL_TRIM | SWT.APPLICATION_MODAL);
3739            shell.setFont(curFont);
3740            shell.setText("Image Value Range");
3741            shell.setImage(ViewProperties.getHdfIcon());
3742            shell.setLayout(new GridLayout(1, true));
3743
3744            Canvas chartCanvas = new Canvas(shell, SWT.DOUBLE_BUFFERED);
3745            GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
3746            gridData.widthHint = 400;
3747            gridData.heightHint = 150;
3748            chartCanvas.setLayoutData(gridData);
3749
3750            final int numberOfPoints = dataDist.length;
3751            int gap = 5;
3752            final int xgap = 2 * gap;
3753            final double xmin = originalRange[0];
3754            final double xmax = originalRange[1];
3755
3756            chartCanvas.addPaintListener(new PaintListener() {
3757                @Override
3758                public void paintControl(PaintEvent e) {
3759                    GC gc = e.gc;
3760
3761                    gc.setFont(curFont);
3762
3763                    int h = rangeH / 3 - 50;
3764                    int w = rangeW;
3765                    int xnpoints = Math.min(10, numberOfPoints - 1);
3766
3767                    // draw the X axis
3768                    gc.drawLine(xgap, h, w + xgap, h);
3769
3770                    // draw x labels
3771                    double xp = 0;
3772                    double x;
3773                    double dw = (double) w / (double) xnpoints;
3774                    double dx = (xmax - xmin) / xnpoints;
3775                    for (int i = 0; i <= xnpoints; i++) {
3776                        x = xmin + i * dx;
3777                        xp = xgap + i * dw;
3778                        gc.drawLine((int) xp, h, (int) xp, h - 5);
3779                        gc.drawString(numberFormat.format(x), (int) xp - 5, h + 20);
3780                    }
3781
3782                    org.eclipse.swt.graphics.Color c = gc.getBackground();
3783                    double yp = 0;
3784                    double ymin = minmaxDist[0];
3785                    double dy = minmaxDist[1] - minmaxDist[0];
3786                    if (dy <= 0)
3787                        dy =1;
3788
3789                    xp = xgap;
3790
3791                    int barWidth = w / numberOfPoints;
3792                    if (barWidth <= 0)
3793                        barWidth = 1;
3794                    dw = (double) w / (double) numberOfPoints;
3795
3796                    gc.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_BLUE));
3797
3798                    for (int j = 0; j < numberOfPoints; j++) {
3799                        xp = xgap + j * dw;
3800                        yp = (int) (h * (dataDist[j] - ymin) / dy);
3801
3802                        gc.fillRectangle((int) xp, (int) (h - yp), barWidth, (int) yp);
3803                    }
3804
3805                    gc.setBackground(c); // set the color back to its default
3806                }
3807            });
3808
3809            org.eclipse.swt.widgets.Group lowerBoundGroup = new org.eclipse.swt.widgets.Group(shell, SWT.NONE);
3810            lowerBoundGroup.setFont(curFont);
3811            lowerBoundGroup.setText("Lower Bound");
3812            lowerBoundGroup.setLayout(new GridLayout(1, true));
3813            lowerBoundGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
3814
3815            minField = new Text(lowerBoundGroup, SWT.SINGLE | SWT.BORDER);
3816            minField.setFont(curFont);
3817            minField.setText(String.valueOf(min));
3818            minField.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
3819            minField.addModifyListener(new ModifyListener() {
3820                @Override
3821                public void modifyText(ModifyEvent e) {
3822                    if (minSlider != null && minSlider.getEnabled()) {
3823                        double value = Double.parseDouble(((Text) e.widget).getText());
3824
3825                        if (value > maxOrg) {
3826                            value = maxOrg;
3827                            minField.setText(String.valueOf(value));
3828                        }
3829
3830                        minSlider.setSelection((int) ((value - minOrg) / tickRatio));
3831                    }
3832                }
3833            });
3834
3835            minSlider = new Slider(lowerBoundGroup, SWT.HORIZONTAL);
3836            minSlider.setMinimum(0);
3837            minSlider.setMaximum(nTICKS);
3838            minSlider.setIncrement(1);
3839            minSlider.setThumb(1);
3840            minSlider.setSelection(0);
3841            minSlider.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
3842            minSlider.addSelectionListener(new SelectionAdapter() {
3843                @Override
3844                public void widgetSelected(SelectionEvent e) {
3845                    double value = minSlider.getSelection();
3846                    double maxValue = maxSlider.getSelection();
3847                    if (value > maxValue) {
3848                        value = maxValue;
3849                        minSlider.setSelection((int) value);
3850                    }
3851
3852                    minField.setText(String.valueOf(value * tickRatio + minOrg));
3853                }
3854            });
3855
3856            org.eclipse.swt.widgets.Group upperBoundGroup = new org.eclipse.swt.widgets.Group(shell, SWT.NONE);
3857            upperBoundGroup.setFont(curFont);
3858            upperBoundGroup.setText("Upper Bound");
3859            upperBoundGroup.setLayout(new GridLayout(1, true));
3860            upperBoundGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
3861
3862            maxField = new Text(upperBoundGroup, SWT.SINGLE | SWT.BORDER);
3863            maxField.setFont(curFont);
3864            maxField.setText(String.valueOf(max));
3865            maxField.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
3866            maxField.addModifyListener(new ModifyListener() {
3867                @Override
3868                public void modifyText(ModifyEvent e) {
3869                    if (maxSlider != null && maxSlider.getEnabled()) {
3870                        double value = Double.parseDouble(((Text) e.widget).getText());
3871
3872                        if (value < minOrg) {
3873                            value = minOrg;
3874                            maxField.setText(String.valueOf(value));
3875                        }
3876
3877                        maxSlider.setSelection((int) ((value - minOrg) / tickRatio));
3878                    }
3879                }
3880            });
3881
3882            maxSlider = new Slider(upperBoundGroup, SWT.HORIZONTAL);
3883            maxSlider.setMinimum(0);
3884            maxSlider.setMaximum(nTICKS);
3885            maxSlider.setIncrement(1);
3886            maxSlider.setThumb(1);
3887            maxSlider.setSelection(nTICKS);
3888            maxSlider.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
3889            maxSlider.addSelectionListener(new SelectionAdapter() {
3890                @Override
3891                public void widgetSelected(SelectionEvent e) {
3892                    double value = maxSlider.getSelection();
3893                    double minValue = minSlider.getSelection();
3894                    if (value < minValue) {
3895                        value = minValue;
3896                        maxSlider.setSelection((int) value);
3897                    }
3898
3899                    maxField.setText(String.valueOf(value * tickRatio + minOrg));
3900                }
3901            });
3902
3903
3904            // Create Ok/Cancel/Apply button region
3905            Composite buttonComposite = new Composite(shell, SWT.NONE);
3906            buttonComposite.setLayout(new GridLayout(3, false));
3907            buttonComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
3908
3909            Button okButton = new Button(buttonComposite, SWT.PUSH);
3910            okButton.setFont(curFont);
3911            okButton.setText("   &OK   ");
3912            okButton.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
3913            okButton.addSelectionListener(new SelectionAdapter() {
3914                @Override
3915                public void widgetSelected(SelectionEvent e) {
3916                    rangeMinMaxCurrent[0] = Double.valueOf(minField.getText());
3917                    rangeMinMaxCurrent[1] = Double.valueOf(maxField.getText());
3918
3919                    shell.dispose();
3920                }
3921            });
3922
3923            Button cancelButton = new Button(buttonComposite, SWT.PUSH);
3924            cancelButton.setFont(curFont);
3925            cancelButton.setText(" &Cancel ");
3926            cancelButton.setLayoutData(new GridData(SWT.CENTER, SWT.FILL, false, false));
3927            cancelButton.addSelectionListener(new SelectionAdapter() {
3928                @Override
3929                public void widgetSelected(SelectionEvent e) {
3930                    rangeMinMaxCurrent[0] = minmaxPrevious[0];
3931                    rangeMinMaxCurrent[1] = minmaxPrevious[1];
3932
3933                    applyDataRange(minmaxPrevious);
3934
3935                    shell.dispose();
3936                }
3937            });
3938
3939            Button applyButton = new Button(buttonComposite, SWT.PUSH);
3940            applyButton.setFont(curFont);
3941            applyButton.setText("&Apply");
3942            applyButton.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, true, false));
3943            applyButton.addSelectionListener(new SelectionAdapter() {
3944                @Override
3945                public void widgetSelected(SelectionEvent e) {
3946                    minmaxPrevious[0] = rangeMinMaxCurrent[0];
3947                    minmaxPrevious[1] = rangeMinMaxCurrent[1];
3948
3949                    rangeMinMaxCurrent[0] = Double.valueOf(minField.getText());
3950                    rangeMinMaxCurrent[1] = Double.valueOf(maxField.getText());
3951
3952                    applyDataRange(rangeMinMaxCurrent);
3953                    rangeMinMaxCurrent[0] = rangeMinMaxCurrent[1] = 0;
3954                }
3955            });
3956
3957            if (min == max) {
3958                minSlider.setEnabled(false);
3959                maxSlider.setEnabled(false);
3960            }
3961
3962
3963            shell.pack();
3964
3965            shell.setSize(shell.computeSize(SWT.DEFAULT, SWT.DEFAULT));
3966
3967            org.eclipse.swt.graphics.Rectangle parentBounds = parent.getBounds();
3968            Point shellSize = shell.getSize();
3969            shell.setLocation((parentBounds.x + (parentBounds.width / 2)) - (shellSize.x / 2),
3970                    (parentBounds.y + (parentBounds.height / 2)) - (shellSize.y / 2));
3971
3972            shell.open();
3973
3974            Display openDisplay = parent.getDisplay();
3975            while(!shell.isDisposed()) {
3976                if (!openDisplay.readAndDispatch())
3977                    openDisplay.sleep();
3978            }
3979        }
3980
3981        public double[] getRange() {
3982            return rangeMinMaxCurrent;
3983        }
3984    }
3985
3986    private class ContrastSlider extends Dialog
3987    {
3988        private Shell shell;
3989        private Scale brightSlider;
3990        private Scale cntrastSlider;
3991        private Text brightField;
3992        private Text contrastField;
3993        private String bLabel = "Brightness";
3994        private String cLabel = "Contrast";
3995
3996        ImageProducer imageProducer;
3997        double[] autoGainBias = {0, 0};
3998        int bLevel = 0;
3999        int cLevel = 0;
4000
4001        public ContrastSlider(Shell parent, int style, ImageProducer producer) {
4002            super(parent, style);
4003
4004            imageProducer = producer;
4005        }
4006
4007        public void open() {
4008            Shell parent = getParent();
4009            shell = new Shell(parent, SWT.SHELL_TRIM | SWT.APPLICATION_MODAL);
4010            shell.setFont(curFont);
4011            shell.setText("Brightness/Contrast");
4012            shell.setImage(ViewProperties.getHdfIcon());
4013            shell.setLayout(new GridLayout(1, true));
4014
4015            if (doAutoGainContrast && gainBias!= null) {
4016                bLabel = "Bias";
4017                cLabel = "Gain";
4018                shell.setText(bLabel + "/" + cLabel);
4019            }
4020
4021            org.eclipse.swt.widgets.Group brightnessGroup = new org.eclipse.swt.widgets.Group(shell, SWT.NONE);
4022            brightnessGroup.setFont(curFont);
4023            brightnessGroup.setText(bLabel + " %");
4024            brightnessGroup.setLayout(new GridLayout(1, true));
4025            brightnessGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
4026
4027            brightField = new Text(brightnessGroup, SWT.SINGLE | SWT.BORDER);
4028            brightField.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
4029            brightField.setFont(curFont);
4030            brightField.setText(String.valueOf(bLevel));
4031            brightField.addListener(SWT.Traverse, new Listener() {
4032                @Override
4033                public void handleEvent(Event e) {
4034                    if (e.detail == SWT.TRAVERSE_RETURN) {
4035                        if (brightSlider != null) {
4036                            double value = Double.parseDouble(((Text) e.widget).getText());
4037
4038                            if (value > 100)
4039                                value = 100;
4040                            else if (value < -100)
4041                                value = -100;
4042
4043                            brightSlider.setSelection((int) value + 100);
4044                        }
4045                    }
4046                }
4047            });
4048
4049
4050            brightSlider = new Scale(brightnessGroup, SWT.HORIZONTAL);
4051            brightSlider.setMinimum(0);
4052            brightSlider.setMaximum(200);
4053            brightSlider.setIncrement(1);
4054            brightSlider.setSelection(bLevel + 100);
4055            brightSlider.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
4056            brightSlider.addSelectionListener(new SelectionAdapter() {
4057                @Override
4058                public void widgetSelected(SelectionEvent e) {
4059                    int value = ((Scale) e.widget).getSelection();
4060                    brightField.setText(String.valueOf(value - 100));
4061                }
4062            });
4063
4064
4065            org.eclipse.swt.widgets.Group contrastGroup = new org.eclipse.swt.widgets.Group(shell, SWT.NONE);
4066            contrastGroup.setFont(curFont);
4067            contrastGroup.setText(cLabel + " %");
4068            contrastGroup.setLayout(new GridLayout(1, true));
4069            contrastGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
4070
4071            contrastField = new Text(contrastGroup, SWT.SINGLE | SWT.BORDER);
4072            contrastField.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
4073            contrastField.setFont(curFont);
4074            contrastField.setText(String.valueOf(cLevel));
4075            contrastField.addListener(SWT.Traverse, new Listener() {
4076                @Override
4077                public void handleEvent(Event e) {
4078                    if (e.detail == SWT.TRAVERSE_RETURN) {
4079                        if (cntrastSlider != null) {
4080                            double value = Double.parseDouble(((Text) e.widget).getText());
4081
4082                            if (value > 100)
4083                                value = 100;
4084                            else if (value < -100)
4085                                value = -100;
4086
4087                            cntrastSlider.setSelection((int) value + 100);
4088                        }
4089                    }
4090                }
4091            });
4092
4093            cntrastSlider = new Scale(contrastGroup, SWT.HORIZONTAL);
4094            cntrastSlider.setMinimum(0);
4095            cntrastSlider.setMaximum(200);
4096            cntrastSlider.setIncrement(1);
4097            cntrastSlider.setSelection(cLevel + 100);
4098            cntrastSlider.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
4099            cntrastSlider.addSelectionListener(new SelectionAdapter() {
4100                @Override
4101                public void widgetSelected(SelectionEvent e) {
4102                    int value = ((Scale) e.widget).getSelection();
4103                    contrastField.setText(String.valueOf(value - 100));
4104                }
4105            });
4106
4107
4108            // Create Ok/Cancel/Apply button region
4109            Composite buttonComposite = new Composite(shell, SWT.NONE);
4110            buttonComposite.setLayout(new GridLayout(3, false));
4111            buttonComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
4112
4113            Button okButton = new Button(buttonComposite, SWT.PUSH);
4114            okButton.setFont(curFont);
4115            okButton.setText("   &OK   ");
4116            okButton.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
4117            okButton.addSelectionListener(new SelectionAdapter() {
4118                @Override
4119                public void widgetSelected(SelectionEvent e) {
4120                    int b = Integer.parseInt(brightField.getText());
4121                    int c = Integer.parseInt(contrastField.getText());
4122
4123                    applyBrightContrast(b, c);
4124
4125                    bLevel = b;
4126                    cLevel = c;
4127
4128                    shell.dispose();
4129                }
4130            });
4131
4132            Button cancelButton = new Button(buttonComposite, SWT.PUSH);
4133            cancelButton.setFont(curFont);
4134            cancelButton.setText(" &Cancel ");
4135            cancelButton.setLayoutData(new GridData(SWT.CENTER, SWT.FILL, false, false));
4136            cancelButton.addSelectionListener(new SelectionAdapter() {
4137                @Override
4138                public void widgetSelected(SelectionEvent e) {
4139                    applyBrightContrast(bLevel, cLevel);
4140                    shell.dispose();
4141                }
4142            });
4143
4144            Button applyButton = new Button(buttonComposite, SWT.PUSH);
4145            applyButton.setFont(curFont);
4146            applyButton.setText("&Apply");
4147            applyButton.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, true, false));
4148            applyButton.addSelectionListener(new SelectionAdapter() {
4149                @Override
4150                public void widgetSelected(SelectionEvent e) {
4151                    int b = Integer.parseInt(brightField.getText());
4152                    int c = Integer.parseInt(contrastField.getText());
4153
4154                    applyBrightContrast(b, c);
4155                }
4156            });
4157
4158
4159            shell.pack();
4160
4161            shell.setSize(shell.computeSize(SWT.DEFAULT, SWT.DEFAULT));
4162
4163            org.eclipse.swt.graphics.Rectangle parentBounds = parent.getBounds();
4164            Point shellSize = shell.getSize();
4165            shell.setLocation((parentBounds.x + (parentBounds.width / 2)) - (shellSize.x / 2),
4166                    (parentBounds.y + (parentBounds.height / 2)) - (shellSize.y / 2));
4167
4168            shell.open();
4169
4170            Display openDisplay = parent.getDisplay();
4171            while(!shell.isDisposed()) {
4172                if (!openDisplay.readAndDispatch())
4173                    openDisplay.sleep();
4174            }
4175        }
4176
4177        private void applyBrightContrast(int blevel, int clevel) {
4178            // separate autogain and simple contrast process
4179            if (doAutoGainContrast && gainBias!= null) {
4180                autoGainBias[0] = gainBias[0]*(1+(clevel)/100.0);
4181                autoGainBias[1] = gainBias[1]*(1+(blevel)/100.0);
4182                applyAutoGain(autoGainBias, null);
4183            }
4184            else {
4185                ImageFilter filter = new BrightnessFilter(blevel, clevel);
4186                image = Tools.toBufferedImage(toolkit.createImage(new FilteredImageSource(imageProducer, filter)));
4187                imageComponent.setImage(image);
4188                zoomTo(zoomFactor);
4189            }
4190        }
4191    }
4192}