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