net.tourbook.photo.internal.manager.PhotoImageLoader.java Source code

Java tutorial

Introduction

Here is the source code for net.tourbook.photo.internal.manager.PhotoImageLoader.java

Source

/*******************************************************************************
 * Copyright (C) 2005, 2016 Wolfgang Schramm and Contributors
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
 *******************************************************************************/
package net.tourbook.photo.internal.manager;

import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Properties;
import java.util.concurrent.LinkedBlockingDeque;

import javax.imageio.ImageIO;

import net.tourbook.common.UI;
import net.tourbook.common.time.TimeTools;
import net.tourbook.common.util.SWT2Dutil;
import net.tourbook.common.util.StatusUtil;
import net.tourbook.photo.ILoadCallBack;
import net.tourbook.photo.ImageQuality;
import net.tourbook.photo.ImageUtils;
import net.tourbook.photo.Photo;
import net.tourbook.photo.PhotoImageCache;
import net.tourbook.photo.PhotoImageMetadata;
import net.tourbook.photo.PhotoLoadManager;
import net.tourbook.photo.PhotoLoadingState;

import org.apache.commons.imaging.ImageReadException;
import org.apache.commons.imaging.common.IImageMetadata;
import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata;
import org.eclipse.core.runtime.IPath;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
import org.imgscalr.Scalr;
import org.imgscalr.Scalr.Method;
import org.imgscalr.Scalr.Rotation;

public class PhotoImageLoader {

    private static String[] awtImageFileSuffixes;

    private Photo _photo;

    private ImageQuality _requestedImageQuality;
    private String _imageFramework;
    private int _hqImageSize;
    private String _requestedImageKey;
    private ILoadCallBack _loadCallBack;

    Display _display;

    /**
     * Contains AWT images which are disposed after loading
     */
    private ArrayList<BufferedImage> _trackedAWTImages = new ArrayList<BufferedImage>();

    /**
     * Contains SWT images which are disposed after loading
     */
    private ArrayList<Image> _trackedSWTImages = new ArrayList<Image>();

    private int[] _recursiveCounter = { 0 };

    static {

        awtImageFileSuffixes = ImageIO.getReaderFileSuffixes();

        //      final String[] formatNames = ImageIO.getReaderFormatNames();
        //      final String mimeReadFormats[] = ImageIO.getReaderMIMETypes();
        //
        //      System.out.println("Mime Reader:      " + Arrays.asList(mimeReadFormats));
        //      System.out.println("Format Reader:    " + Arrays.asList(formatNames));
        //      System.out.println("Suffixes Readers: " + Arrays.asList(awtImageFileSuffixes));

        //      final String writeFormats[] = ImageIO.getWriterMIMETypes();
        //      System.out.println("Mime Writers:     " + Arrays.asList(writeFormats));

    }

    public PhotoImageLoader(final Display display, final Photo photo, final ImageQuality imageQuality,
            final String imageFramework, final int hqImageSize, final ILoadCallBack loadCallBack) {

        //      System.out.println(UI.timeStampNano() + " PhotoImageLoader\tthread:" + Thread.currentThread().getName());
        //      // TODO remove SYSTEM.OUT.PRINTLN

        _display = display;
        _photo = photo;
        _requestedImageQuality = imageQuality;
        _imageFramework = imageFramework;
        _hqImageSize = hqImageSize;
        _loadCallBack = loadCallBack;

        _requestedImageKey = photo.getImageKey(_requestedImageQuality);
    }

    private Image createSWTimageFromAWTimage(final BufferedImage awtBufferedImage, final String imageFilePath) {

        //      final ImageData swtImageData = UI.convertAWTimageIntoSWTimage(awtBufferedImage, imageFilePath);

        final ImageData swtImageData = SWT2Dutil.convertToSWT(awtBufferedImage, imageFilePath);

        if (swtImageData != null) {
            // image could be converted
            return new Image(_display, swtImageData);
        }

        /*
         * try to convert it to a jpg file
         */

        String tempFilename = null;
        Image swtThumbnailImage = null;

        try {

            // get temp file name
            final File tempFile = File.createTempFile("prefix", UI.EMPTY_STRING);//$NON-NLS-1$
            tempFilename = tempFile.getName();
            tempFile.delete();

            ImageIO.write(awtBufferedImage, ThumbnailStore.THUMBNAIL_IMAGE_EXTENSION_JPG, new File(tempFilename));

        } catch (final Exception e) {

            StatusUtil.log(NLS.bind(//
                    "Cannot save thumbnail image with AWT: \"{0}\"", //$NON-NLS-1$
                    imageFilePath), e);
        } finally {

            try {

                // get SWT image from saved AWT image
                swtThumbnailImage = new Image(_display, tempFilename);

            } catch (final Exception e) {

                StatusUtil.log(NLS.bind(//
                        "Cannot load thumbnail image with SWT: \"{0}\"", //$NON-NLS-1$
                        tempFilename), e);
            } finally {

                // remove temp tile
                new File(tempFilename).delete();
            }
        }

        return swtThumbnailImage;
    }

    private void disposeTrackedImages() {

        for (final BufferedImage awtImage : _trackedAWTImages) {
            if (awtImage != null) {
                awtImage.flush();
            }
        }
        _trackedAWTImages.clear();

        for (final Image swtImage : _trackedSWTImages) {
            if (swtImage != null) {
                swtImage.dispose();
            }
        }
        _trackedSWTImages.clear();
    }

    public Photo getPhoto() {
        return _photo;
    }

    public ImageQuality getRequestedImageQuality() {
        return _requestedImageQuality;
    }

    /**
     * @return Returns roatation according to the EXIF data or <code>null</code> when image is not
     *         rotatet.
     */
    private Rotation getRotation() {

        Rotation thumbRotation = null;
        final int orientation = _photo.getOrientation();

        if (orientation > 1) {

            // see here http://www.impulseadventure.com/photo/exif-orientation.html

            if (orientation == 8) {
                thumbRotation = Rotation.CW_270;
            } else if (orientation == 3) {
                thumbRotation = Rotation.CW_180;
            } else if (orientation == 6) {
                thumbRotation = Rotation.CW_90;
            }
        }

        return thumbRotation;
    }

    /**
     * @return Returns <code>true</code> when the image file can be loaded with AWT
     */
    private boolean isAWTImageSupported() {

        final String photoSuffix = _photo.imageFileExt;

        for (final String awtImageSuffix : awtImageFileSuffixes) {
            if (photoSuffix.equals(awtImageSuffix)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param storeImageFilePath
     *            Path to store image in the thumbnail store
     * @return
     */
    private Image loadImageFromEXIFThumbnail(final IPath storeImageFilePath) {

        BufferedImage awtBufferedImage = null;

        try {

            // read exif meta data
            final IImageMetadata metadata = _photo.getImageMetaData(true);

            if (metadata == null) {
                return null;
            }

            // this will print out all metadata
            //         System.out.println(metadata);

            if (metadata instanceof JpegImageMetadata) {

                awtBufferedImage = ((JpegImageMetadata) metadata).getEXIFThumbnail();

                _trackedAWTImages.add(awtBufferedImage);

                if (awtBufferedImage == null) {
                    return null;
                }

                Image swtThumbnailImage = null;
                try {

                    /*
                     * transform EXIF image and save it in the thumb store
                     */
                    try {

                        awtBufferedImage = transformImageCrop(awtBufferedImage);
                        awtBufferedImage = transformImageRotate(awtBufferedImage);

                    } catch (final Exception e) {
                        StatusUtil.log(NLS.bind(//
                                "Image \"{0}\" cannot be resized", //$NON-NLS-1$
                                _photo.imageFilePathName), e);
                        return null;
                    }

                    swtThumbnailImage = createSWTimageFromAWTimage(awtBufferedImage,
                            storeImageFilePath.toOSString());

                    // set state after creating image, this could cause an error
                    _photo.setStateExifThumb(swtThumbnailImage == null ? 0 : 1);

                    if (swtThumbnailImage != null) {
                        return swtThumbnailImage;
                    }

                } catch (final Exception e) {
                    StatusUtil.log(NLS.bind(//
                            "SWT store image \"{0}\" cannot be created", //$NON-NLS-1$
                            storeImageFilePath.toOSString()), e);
                } finally {

                    if (swtThumbnailImage == null) {

                        System.out.println(NLS.bind( //
                                UI.timeStampNano() + " EXIF image \"{0}\" cannot be created", //$NON-NLS-1$
                                storeImageFilePath));
                    }
                }
            }
        } catch (final ImageReadException e) {
            StatusUtil.log(e);
        } catch (final IOException e) {
            StatusUtil.log(e);
        }

        return null;
    }

    private void loadImageFromEXIFThumbnailOriginal() {

        Image loadedExifImage = null;
        String imageKey = null;

        try {

            // get image from thumbnail image in the EXIF data

            final IPath storeThumbImageFilePath = ThumbnailStore.getStoreImagePath(_photo, ImageQuality.THUMB);

            final Image exifThumbnail = loadImageFromEXIFThumbnail(storeThumbImageFilePath);
            if (exifThumbnail != null) {

                // EXIF image is available

                imageKey = _photo.getImageKey(ImageQuality.THUMB);
                loadedExifImage = exifThumbnail;
            }

        } catch (final Exception e) {

        } finally {

            disposeTrackedImages();

            if (loadedExifImage != null) {

                // cache loaded thumb, that the redraw finds the image

                final String originalImagePathName = _photo.imageFilePathName;

                PhotoImageCache.putImage(imageKey, loadedExifImage, originalImagePathName);

                // display image in the loading callback
                //            _loadCallBack.callBackImageIsLoaded(true);
            }
        }
    }

    /**
     * Get image from thumb store with the requested image quality.
     * 
     * @param _photo
     * @param requestedImageQuality
     * @return
     */
    private Image loadImageFromStore(final ImageQuality requestedImageQuality) {

        /*
         * check if image is available in the thumbstore
         */
        final IPath requestedStoreImageFilePath = ThumbnailStore.getStoreImagePath(_photo, requestedImageQuality);

        final String imageStoreFilePath = requestedStoreImageFilePath.toOSString();
        final File storeImageFile = new File(imageStoreFilePath);

        if (storeImageFile.isFile() == false) {
            return null;
        }

        // photo image is available in the thumbnail store

        Image storeImage = null;

        /*
         * touch store file when it is not yet done today, this is done to track last access time so
         * that a store cleanup can check the date
         */
        final LocalDate dtModified = TimeTools.getZonedDateTime(storeImageFile.lastModified()).toLocalDate();

        if (dtModified.equals(LocalDate.now()) == false) {

            storeImageFile.setLastModified(TimeTools.now().toInstant().toEpochMilli());
        }

        try {

            storeImage = new Image(_display, imageStoreFilePath);

            loadImageProperties(requestedStoreImageFilePath);

        } catch (final Exception e) {
            StatusUtil.log(NLS.bind("Image cannot be loaded with SWT (1): \"{0}\"", //$NON-NLS-1$
                    imageStoreFilePath), e);
        } finally {

            if (storeImage == null) {

                String message = "Image \"{0}\" cannot be loaded and an exception did not occure.\n" //$NON-NLS-1$
                        + "The image file is available but it's possible that SWT.ERROR_NO_HANDLES occured"; //$NON-NLS-1$

                System.out.println(UI.timeStampNano() + NLS.bind(message, imageStoreFilePath));

                PhotoImageCache.disposeThumbs(null);

                /*
                 * try loading again
                 */
                try {

                    storeImage = new Image(_display, imageStoreFilePath);

                } catch (final Exception e) {
                    StatusUtil.log(NLS.bind("Image cannot be loaded with SWT (2): \"{0}\"", //$NON-NLS-1$
                            imageStoreFilePath), e);
                } finally {

                    if (storeImage == null) {

                        message = "Image cannot be loaded again with SWT, even when disposing the image cache: \"{0}\" "; //$NON-NLS-1$

                        System.out.println(UI.timeStampNano() + NLS.bind(message, imageStoreFilePath));
                    }
                }
            }
        }

        return storeImage;
    }

    /**
     * Image could not be loaded with {@link #loadImage()}, try to load high quality image.
     * 
     * @param thumbImageWaitingQueue
     *            waiting queue for small images
     * @param exifWaitingQueue
     */
    public void loadImageHQ(final LinkedBlockingDeque<PhotoImageLoader> thumbImageWaitingQueue,
            final LinkedBlockingDeque<PhotoExifLoader> exifWaitingQueue) {

        //      if (isImageVisible() == false) {
        //         setStateUndefined();
        //         return;
        //      }

        /*
         * wait until exif data and small images are loaded
         */
        try {
            while (thumbImageWaitingQueue.size() > 0 || exifWaitingQueue.size() > 0) {
                Thread.sleep(PhotoLoadManager.DELAY_TO_CHECK_WAITING_QUEUE);
            }
        } catch (final InterruptedException e) {
            // should not happen, I hope so
        }

        boolean isLoadingError = false;
        Image hqImage = null;

        try {

            /**
             * sometimes (when images are loaded concurrently) larger images could not be loaded
             * with SWT methods in Win7 (Eclipse 3.8 M6), try to load image with AWT. This bug fix
             * <code>https://bugs.eclipse.org/bugs/show_bug.cgi?id=350783</code> has not solved this
             * problem
             */

            // load original image and create thumbs

            if (_imageFramework.equals(PhotoLoadManager.IMAGE_FRAMEWORK_SWT)
                    // use SWT when image format is not supported by AWT which is the case for tiff images
                    || isAWTImageSupported() == false) {

                hqImage = loadImageHQ_10_WithSWT();
            } else {
                hqImage = loadImageHQ_20_WithAWT();
            }

        } catch (final Exception e) {

            setStateLoadingError();

            isLoadingError = true;

        } finally {

            disposeTrackedImages();

            if (hqImage == null) {

                System.out.println(NLS.bind(//
                        UI.timeStampNano() + " image == NULL when loading with {0}: \"{1}\"", //$NON-NLS-1$
                        _imageFramework.toUpperCase(), _photo.imageFilePathName));

                if (_imageFramework.equals(PhotoLoadManager.IMAGE_FRAMEWORK_AWT)) {

                    /*
                     * AWT fails, try to load image with SWT
                     */

                    try {

                        hqImage = loadImageHQ_10_WithSWT();

                    } catch (final Exception e2) {

                        setStateLoadingError();

                        isLoadingError = true;

                    } finally {

                        if (hqImage == null) {
                            System.out.println(NLS.bind(//
                                    UI.timeStampNano() + " image == NULL when loading with SWT: \"{0}\"", //$NON-NLS-1$
                                    _photo.imageFilePathName));
                        }
                    }
                }
            }

            // update image state
            final boolean isImageLoaded = hqImage != null;
            if (isImageLoaded) {

                setStateUndefined();

            } else {

                setStateLoadingError();

                isLoadingError = true;
            }

            // display image in the loading callback
            _loadCallBack.callBackImageIsLoaded(isImageLoaded || isLoadingError);
        }
    }

    private Image loadImageHQ_10_WithSWT() throws Exception {

        if (_recursiveCounter[0]++ > 2) {
            return null;
        }

        final long start = System.currentTimeMillis();
        long endHqLoad = 0;
        long endResizeHQ = 0;
        long endResizeThumb = 0;
        long endSaveHQ = 0;
        long endSaveThumb = 0;

        Image originalImage = null;

        /*
         * load original image
         */
        final String originalImagePathName = _photo.imageFilePathName;
        try {

            final long startHqLoad = System.currentTimeMillis();

            originalImage = new Image(_display, originalImagePathName);

            endHqLoad = System.currentTimeMillis() - startHqLoad;

        } catch (final Exception e) {

            System.out.println(NLS.bind(//
                    "SWT: image \"{0}\" cannot be loaded", //$NON-NLS-1$
                    originalImagePathName));

        } finally {

            if (originalImage == null) {

                System.out.println(NLS.bind( //
                        UI.timeStampNano() + " SWT: image \"{0}\" cannot be loaded, will load with AWT", //$NON-NLS-1$
                        originalImagePathName));

                /**
                 * sometimes (when images are loaded concurrently) larger images could not be loaded
                 * with SWT methods in Win7 (Eclipse 3.8 M6), try to load image with AWT. This bug
                 * fix <code>
                 * https://bugs.eclipse.org/bugs/show_bug.cgi?id=350783
                 * https://bugs.eclipse.org/bugs/show_bug.cgi?id=375845
                 * </code>
                 * has not solved this problem
                 */

                try {
                    return loadImageHQ_20_WithAWT();
                } catch (final Exception e) {
                    throw e;
                }
            } else {
            }
        }

        Rectangle imageBounds = originalImage.getBounds();

        final int originalImageWidth = imageBounds.width;
        final int originalImageHeight = imageBounds.height;

        int imageWidth = originalImageWidth;
        int imageHeight = originalImageHeight;

        final Properties originalImageProperties = new Properties();
        originalImageProperties.put(ThumbnailStore.ORIGINAL_IMAGE_WIDTH, Integer.toString(originalImageWidth));
        originalImageProperties.put(ThumbnailStore.ORIGINAL_IMAGE_HEIGHT, Integer.toString(originalImageHeight));

        // update dimension
        updateImageSize(imageWidth, imageHeight, true);

        final int thumbSize = PhotoLoadManager.IMAGE_SIZE_THUMBNAIL;

        // images are rotated only ONE time (the first one)
        boolean isRotated = false;

        boolean isHQCreated = false;

        Image hqImage;
        Image requestedSWTImage = null;

        /*
         * create HQ image
         */
        if (imageWidth > _hqImageSize || imageHeight > _hqImageSize) {

            /*
             * image is larger than HQ image -> resize to HQ
             */

            final long startResizeHQ = System.currentTimeMillis();

            final Point bestSize = ImageUtils.getBestSize(imageWidth, imageHeight, _hqImageSize, _hqImageSize);

            Rotation hqRotation = null;

            if (isRotated == false) {
                isRotated = true;
                hqRotation = getRotation();
            }

            final Image scaledHQImage = ImageUtils.resize(_display, originalImage, bestSize.x, bestSize.y, SWT.ON,
                    SWT.LOW, hqRotation);

            endResizeHQ = System.currentTimeMillis() - startResizeHQ;

            hqImage = scaledHQImage;

            imageBounds = scaledHQImage.getBounds();
            imageWidth = imageBounds.width;
            imageHeight = imageBounds.height;

            // new image has been created, loaded must be disposed
            _trackedSWTImages.add(originalImage);

            if (_requestedImageQuality == ImageQuality.HQ) {
                // keep scaled image
                requestedSWTImage = scaledHQImage;
            } else {
                // dispose scaled image
                _trackedSWTImages.add(scaledHQImage);
            }

            /*
             * save scaled image in store
             */
            final long startSaveHQ = System.currentTimeMillis();
            final IPath storeHQImagePath = ThumbnailStore.getStoreImagePath(_photo, ImageQuality.HQ);

            ThumbnailStore.saveThumbImageWithSWT(scaledHQImage, storeHQImagePath, originalImageProperties);

            isHQCreated = true;

            endSaveHQ = System.currentTimeMillis() - startSaveHQ;

        } else {
            hqImage = originalImage;
        }

        /*
         * create thumb image
         */
        if (imageWidth > thumbSize || imageHeight > thumbSize) {

            /*
             * image is larger than thumb image -> resize to thumb
             */

            if (isHQCreated == false) {

                // image size is between thumb and HQ

                if (_requestedImageQuality == ImageQuality.HQ) {
                    requestedSWTImage = hqImage;
                }
            }

            if (_photo.getExifThumbImageState() == 1) {

                if (requestedSWTImage == null) {

                    // get thumb image

                    final IPath storeImageFilePath = ThumbnailStore.getStoreImagePath(_photo, ImageQuality.THUMB);

                    requestedSWTImage = loadImageFromEXIFThumbnail(storeImageFilePath);
                }

            } else {

                // create thumb image

                final long startResizeThumb = System.currentTimeMillis();

                final Point bestSize = ImageUtils.getBestSize(imageWidth, imageHeight, thumbSize, thumbSize);
                Rotation thumbRotation = null;

                if (isRotated == false) {
                    isRotated = true;
                    thumbRotation = getRotation();
                }

                final Image scaledThumbImage = ImageUtils.resize(_display, hqImage, bestSize.x, bestSize.y, SWT.ON,
                        SWT.LOW, thumbRotation);

                /*
                 * new image has been created, source image must be disposed when it's not the
                 * requested image
                 */
                if (requestedSWTImage != hqImage) {
                    _trackedSWTImages.add(hqImage);
                }

                if (requestedSWTImage == null) {
                    // keep scaled image
                    requestedSWTImage = scaledThumbImage;
                } else {
                    // dispose scaled image
                    _trackedSWTImages.add(scaledThumbImage);
                }

                endResizeThumb = System.currentTimeMillis() - startResizeThumb;

                /*
                 * save scaled image in store
                 */
                final long startSaveThumb = System.currentTimeMillis();
                final IPath storeThumbImagePath = ThumbnailStore.getStoreImagePath(_photo, ImageQuality.THUMB);

                ThumbnailStore.saveThumbImageWithSWT(scaledThumbImage, storeThumbImagePath,
                        originalImageProperties);

                endSaveThumb = System.currentTimeMillis() - startSaveThumb;
            }

        } else {

            // loaded image is smaller than a thumb image

            requestedSWTImage = originalImage;
        }

        if (requestedSWTImage != null) {

            // ensure metadata are loaded
            _photo.getImageMetaData();

            // keep requested image in cache
            PhotoImageCache.putImage(_requestedImageKey, requestedSWTImage, originalImagePathName);
        }

        if (requestedSWTImage == null) {
            setStateLoadingError();
        }

        final long end = System.currentTimeMillis() - start;

        System.out.println(UI.timeStampNano() + " SWT: " //$NON-NLS-1$
                + (Thread.currentThread().getName() + " " + _photo.imageFileName) //$NON-NLS-1$
                + ("\ttotal: " + end) //$NON-NLS-1$
                + ("\tload: " + endHqLoad) //$NON-NLS-1$
                + ("\tresizeHQ: " + endResizeHQ) //$NON-NLS-1$
                + ("\tsaveHQ: " + endSaveHQ) //$NON-NLS-1$
                + ("\tresizeThumb: " + endResizeThumb) //$NON-NLS-1$
                + ("\tsaveThumb: " + endSaveThumb)); //$NON-NLS-1$

        return requestedSWTImage;
    }

    private Image loadImageHQ_20_WithAWT() throws Exception {

        if (_recursiveCounter[0]++ > 2) {
            return null;
        }

        final long start = System.currentTimeMillis();
        long endHqLoad = 0;
        long endResizeHQ = 0;
        long endResizeThumb = 0;
        long endSaveHQ = 0;
        long endSaveThumb = 0;

        Image requestedSWTImage = null;
        String exceptionMessage = null;

        // images are rotated only ONCE (the first one)
        boolean isRotated = false;

        /*
         * load original image
         */
        BufferedImage awtOriginalImage = null;
        final String originalImagePathName = _photo.imageFilePathName;
        try {

            final long startHqLoad = System.currentTimeMillis();
            {
                awtOriginalImage = ImageIO.read(_photo.imageFile);

                _trackedAWTImages.add(awtOriginalImage);
            }
            endHqLoad = System.currentTimeMillis() - startHqLoad;

        } catch (final Exception e) {

            StatusUtil.log(NLS.bind("AWT: image \"{0}\" cannot be loaded.", originalImagePathName)); //$NON-NLS-1$

        } finally {

            if (awtOriginalImage == null) {

                System.out.println(NLS.bind(//
                        UI.timeStampNano() + " AWT: image \"{0}\" cannot be loaded, will load with SWT", //$NON-NLS-1$
                        originalImagePathName));

                return loadImageHQ_10_WithSWT();
            }
        }

        /*
         * handle thumb save error
         */
        final boolean isThumbSaveError = PhotoLoadManager.isThumbSaveError(originalImagePathName);
        if (isThumbSaveError) {

            // the thumb image could not be previously saved in the thumb store, display original image

            final Image swtImage = createSWTimageFromAWTimage(awtOriginalImage, originalImagePathName);

            if (swtImage == null) {

                exceptionMessage = NLS.bind(//
                        "Photo image with thumb save error cannot be created with SWT (1): ", //$NON-NLS-1$
                        originalImagePathName);
            } else {

                requestedSWTImage = swtImage;
            }

        } else {

            /*
             * create HQ image from original image
             */

            boolean isHQCreated = false;

            final int originalImageWidth = awtOriginalImage.getWidth();
            final int originalImageHeight = awtOriginalImage.getHeight();

            final Properties originalImageProperties = new Properties();
            originalImageProperties.put(ThumbnailStore.ORIGINAL_IMAGE_WIDTH, Integer.toString(originalImageWidth));
            originalImageProperties.put(ThumbnailStore.ORIGINAL_IMAGE_HEIGHT,
                    Integer.toString(originalImageHeight));

            int imageWidth = originalImageWidth;
            int imageHeight = originalImageHeight;

            // update dimension
            updateImageSize(imageWidth, imageHeight, true);

            BufferedImage hqImage;

            if (imageWidth >= _hqImageSize || imageHeight >= _hqImageSize) {

                // original image is larger than HQ image -> resize to HQ

                BufferedImage scaledHQImage;

                final long startResizeHQ = System.currentTimeMillis();
                {
                    final Point bestSize = ImageUtils.getBestSize(imageWidth, imageHeight, _hqImageSize,
                            _hqImageSize);
                    final int maxSize = Math.max(bestSize.x, bestSize.y);

                    scaledHQImage = Scalr.resize(awtOriginalImage, Method.SPEED, maxSize);

                    _trackedAWTImages.add(scaledHQImage);

                    // rotate image according to the EXIF flag
                    if (isRotated == false) {
                        isRotated = true;

                        scaledHQImage = transformImageRotate(scaledHQImage);
                    }

                    hqImage = scaledHQImage;

                    imageWidth = scaledHQImage.getWidth();
                    imageHeight = scaledHQImage.getHeight();
                }
                endResizeHQ = System.currentTimeMillis() - startResizeHQ;

                /*
                 * save scaled HQ image in store
                 */
                final long startSaveHQ = System.currentTimeMillis();
                {
                    final boolean isSaved = ThumbnailStore.saveThumbImageWithAWT(scaledHQImage,
                            ThumbnailStore.getStoreImagePath(_photo, ImageQuality.HQ), originalImageProperties);

                    if (isSaved == false) {
                        // AWT save error has occured, possible error: "Bogus input colorspace"
                        _photo.setThumbSaveError();

                    }

                    // check if the scaled image has the requested image quality
                    if (_requestedImageQuality == ImageQuality.HQ) {

                        // create swt image from saved AWT image, this converts AWT -> SWT

                        requestedSWTImage = loadImageFromStore(ImageQuality.HQ);
                    }
                }
                endSaveHQ = System.currentTimeMillis() - startSaveHQ;

                isHQCreated = true;

            } else {
                hqImage = awtOriginalImage;
            }

            /*
             * create thumb image from HQ image
             */
            BufferedImage saveThumbAWT = null;

            final int thumbSize = PhotoLoadManager.IMAGE_SIZE_THUMBNAIL;
            if (imageWidth >= thumbSize || imageHeight >= thumbSize) {

                /*
                 * image is larger than thumb image -> resize to thumb
                 */

                if (isHQCreated == false) {

                    // image size is between thumb and HQ

                    if (_requestedImageQuality == ImageQuality.HQ) {
                        requestedSWTImage = createSWTimageFromAWTimage(awtOriginalImage, originalImagePathName);
                    }
                }

                if (_photo.getExifThumbImageState() == 1) {

                    if (requestedSWTImage == null) {

                        // get thumb image

                        final IPath storeImageFilePath = ThumbnailStore.getStoreImagePath(_photo,
                                ImageQuality.THUMB);
                        requestedSWTImage = loadImageFromEXIFThumbnail(storeImageFilePath);
                    }

                } else {

                    // check if thumb image is already available
                    final Image exifThumbImage = loadImageFromStore(ImageQuality.THUMB);
                    if (exifThumbImage != null) {

                        // EXIF thumb image is already available in the thumbstore

                        if (requestedSWTImage == null && _requestedImageQuality == ImageQuality.THUMB) {

                            requestedSWTImage = exifThumbImage;
                        } else {
                            _trackedSWTImages.add(exifThumbImage);
                        }

                    } else {

                        /*
                         * create thumb image
                         */

                        BufferedImage scaledThumbImage;
                        final long startResizeThumb = System.currentTimeMillis();
                        {
                            final Point bestSize = ImageUtils.getBestSize(imageWidth, imageHeight, thumbSize,
                                    thumbSize);
                            final int maxSize = Math.max(bestSize.x, bestSize.y);

                            scaledThumbImage = Scalr.resize(hqImage, Method.QUALITY, maxSize);

                            _trackedAWTImages.add(scaledThumbImage);

                            // rotate image according to the exif flag
                            if (isRotated == false) {

                                isRotated = true;

                                scaledThumbImage = transformImageRotate(scaledThumbImage);
                            }

                            saveThumbAWT = scaledThumbImage;
                        }
                        endResizeThumb = System.currentTimeMillis() - startResizeThumb;
                    }
                }

            } else {

                // loaded image is smaller than a thumb image

                saveThumbAWT = awtOriginalImage;
            }

            /*
             * save thumb image
             */
            if (saveThumbAWT == awtOriginalImage) {

                // original image is not saved as a thumb

                if (requestedSWTImage == null) {

                    requestedSWTImage = createSWTimageFromAWTimage(saveThumbAWT, originalImagePathName);

                    if (requestedSWTImage == null) {
                        exceptionMessage = NLS.bind(//
                                "Photo image cannot be converted from AWT to SWT: ", //$NON-NLS-1$
                                originalImagePathName);
                    }
                }

            } else {

                boolean isSaved = true;

                if (saveThumbAWT != null) {

                    final long startSaveThumb = System.currentTimeMillis();
                    {
                        final IPath storeThumbImagePath = ThumbnailStore.getStoreImagePath(_photo,
                                ImageQuality.THUMB);

                        isSaved = ThumbnailStore.saveThumbImageWithAWT(saveThumbAWT, storeThumbImagePath,
                                originalImageProperties);
                    }
                    endSaveThumb = System.currentTimeMillis() - startSaveThumb;
                }

                if (isSaved == false) {

                    // AWT save error has occured, possible error: "Bogus input colorspace"
                    _photo.setThumbSaveError();

                    if (requestedSWTImage == null) {

                        requestedSWTImage = createSWTimageFromAWTimage(saveThumbAWT, originalImagePathName);

                        if (requestedSWTImage == null) {
                            exceptionMessage = NLS.bind(
                                    "Photo image with thumb save error cannot be created with SWT (2): ", //$NON-NLS-1$
                                    originalImagePathName);
                        }
                    }
                }
            }

            // check if the requested image is set, if not load thumb
            if (requestedSWTImage == null) {

                // create swt image from saved AWT image (convert AWT->SWT)

                requestedSWTImage = loadImageFromStore(ImageQuality.THUMB);
            }

            if (requestedSWTImage != null) {

                // ensure metadata are loaded
                _photo.getImageMetaData();

                // keep requested image in cache
                PhotoImageCache.putImage(_requestedImageKey, requestedSWTImage, originalImagePathName);
            }

            if (requestedSWTImage == null) {
                setStateLoadingError();
            }
        }

        final long end = System.currentTimeMillis() - start;

        System.out.println(UI.timeStampNano() + " AWT: " //$NON-NLS-1$
                + (Thread.currentThread().getName() + " " + _photo.imageFileName) //$NON-NLS-1$
                + ("\ttotal: " + end) //$NON-NLS-1$
                + ("\tload: " + endHqLoad) //$NON-NLS-1$
                + ("\tresizeHQ: " + endResizeHQ) //$NON-NLS-1$
                + ("\tsaveHQ: " + endSaveHQ) //$NON-NLS-1$
                + ("\tresizeThumb: " + endResizeThumb) //$NON-NLS-1$
                + ("\tsaveThumb: " + endSaveThumb) //$NON-NLS-1$
        //
        );

        if (exceptionMessage != null) {
            throw new Exception(exceptionMessage);
        }

        return requestedSWTImage;
    }

    public void loadImageOriginal() {

        final long start = System.currentTimeMillis();
        long endOriginalLoad1 = 0;
        long endOriginalLoad2 = 0;
        long endRotate = 0;

        /*
         * display thumb image during loading the original when it's not in the cache, when it's in
         * the cache, the thumb is already displayed
         */
        final Image photoImage = PhotoImageCache.getImage(_photo, ImageQuality.THUMB);
        if (photoImage == null || photoImage.isDisposed()) {
            loadImageFromEXIFThumbnailOriginal();
        }

        /*
         * ensure metadata are loaded, is needed for image rotation, it can be not loaded when many
         * images are in the gallery and loading exif data has not yet finished
         */
        final PhotoImageMetadata imageMetaData = _photo.getImageMetaData();

        boolean isLoadingException = false;
        Image swtImage = null;
        final String originalImagePathName = _photo.imageFilePathName;

        try {

            /*
             * 1st try: load original image only with SWT
             */
            try {

                final long startLoading = System.currentTimeMillis();

                swtImage = new Image(_display, originalImagePathName);

                endOriginalLoad1 = System.currentTimeMillis() - startLoading;

            } catch (final Exception e) {

                isLoadingException = true;

                System.out.println(NLS.bind(//
                        "SWT: image \"{0}\" cannot be loaded (1)", //$NON-NLS-1$
                        originalImagePathName));

            } finally {

                if (swtImage == null) {

                    /*
                     * 2nd try
                     */

                    System.out.println(NLS.bind( //
                            UI.timeStampNano() + " SWT: image \"{0}\" cannot be loaded (2)", //$NON-NLS-1$
                            originalImagePathName));

                    /**
                     * sometimes (when images are loaded concurrently) larger images could not be
                     * loaded with SWT methods in Win7 (Eclipse 3.8 M6), try to load image with AWT.
                     * This bug fix <code>
                     * https://bugs.eclipse.org/bugs/show_bug.cgi?id=350783
                     * https://bugs.eclipse.org/bugs/show_bug.cgi?id=375845
                     * </code>
                     * has not solved this problem
                     */

                    PhotoImageCache.disposeOriginal(null);
                    PhotoImageCache.disposeThumbs(null);

                    try {

                        final long startLoading = System.currentTimeMillis();

                        swtImage = new Image(_display, originalImagePathName);

                        endOriginalLoad2 = System.currentTimeMillis() - startLoading;

                    } catch (final Exception e) {

                        isLoadingException = true;

                        System.out.println(NLS.bind(//
                                "SWT: image \"{0}\" cannot be loaded (3)", //$NON-NLS-1$
                                originalImagePathName));

                    } finally {

                        if (swtImage == null) {
                            System.out.println(NLS.bind( //
                                    UI.timeStampNano() + " SWT: image \"{0}\" cannot be loaded (4)", //$NON-NLS-1$
                                    originalImagePathName));
                        }
                    }
                }
            }

        } finally {

            /**
             * The not thrown NO MORE HANDLE EXCEPTION cannot be catched therefore the check:
             * swtImage == null
             */

            disposeTrackedImages();

            if (swtImage != null) {

                final Rectangle imageBounds = swtImage.getBounds();
                final int imageWidth = imageBounds.width;
                final int imageHeight = imageBounds.height;

                // update dimension
                updateImageSize(imageWidth, imageHeight, true);

                /*
                 * rotate image when necessary
                 */

                final Rotation rotation = getRotation();
                if (rotation != null) {

                    // image must be rotated

                    final long startRotate = System.currentTimeMillis();

                    final Image rotatedImage = ImageUtils.resize(_display, swtImage, imageWidth, imageHeight,
                            SWT.ON, SWT.LOW, rotation);

                    swtImage.dispose();
                    swtImage = rotatedImage;

                    endRotate = System.currentTimeMillis() - startRotate;
                }

                // keep requested image in cache
                PhotoImageCache.putImageOriginal(_requestedImageKey, swtImage, originalImagePathName);
            }

            if (isLoadingException) {
                setStateLoadingError();

            } else {

                // image is loaded with requested quality or a SWT error has occured, reset image state
                setStateUndefined();
            }

            final long end = System.currentTimeMillis() - start;

            System.out.println(UI.timeStampNano() + " SWT: " //$NON-NLS-1$
                    + Thread.currentThread().getName() + " " //$NON-NLS-1$
                    + _photo.imageFileName + ("\ttotal: " + end) //$NON-NLS-1$
                    + ("\tload1: " + endOriginalLoad1) //$NON-NLS-1$
                    + ("\tload2: " + endOriginalLoad2) //$NON-NLS-1$
                    + ("\trotate: " + endRotate) //$NON-NLS-1$
            //
            );

            // display image in the loading callback
            _loadCallBack.callBackImageIsLoaded(true);
        }
    }

    /**
     * @param requestedStoreImageFilePath
     * @return Returns <code>null</code> when properties cannot be loaded.
     */
    private void loadImageProperties(final IPath requestedStoreImageFilePath) {

        final IPath propPath = ThumbnailStore.getPropertiesPathFromImagePath(requestedStoreImageFilePath);
        FileInputStream fileStream = null;

        Properties imageProperties = null;

        try {

            final File propFile = propPath.toFile();

            if (propFile.isFile() == false) {
                return;
            }

            fileStream = new FileInputStream(propPath.toOSString());

            imageProperties = new Properties();

            imageProperties.load(fileStream);

        } catch (final IOException e) {
            StatusUtil.log(NLS.bind("Image properties cannot be loaded from: \"{0}\"", //$NON-NLS-1$
                    propPath.toOSString()), e);
        } finally {

            if (fileStream != null) {
                try {
                    fileStream.close();
                } catch (final IOException e) {
                    StatusUtil.log(e);
                }
            }
        }

        if (imageProperties != null) {

            final String originalImageWidth = imageProperties.getProperty(ThumbnailStore.ORIGINAL_IMAGE_WIDTH);
            final String originalImageHeight = imageProperties.getProperty(ThumbnailStore.ORIGINAL_IMAGE_HEIGHT);

            if (originalImageWidth != null && originalImageHeight != null) {
                try {

                    final int width = Integer.parseInt(originalImageWidth);
                    final int height = Integer.parseInt(originalImageHeight);

                    _photo.setPhotoDimension(width, height);

                } catch (final NumberFormatException e) {
                    StatusUtil.log(e);
                }
            }
        }

    }

    // JAI implementation to read tiff images with AWT
    //
    //   private BufferedImage loadImageHQ_22_ExtendedAWT(final PhotoWrapper photoWrapper) throws IOException {
    //
    //      if (isAWTImageSupported == false) {
    //
    //         // extension is not supported
    //
    //         return null;
    //
    ////
    ////         final SeekableStream s = new FileSeekableStream(_photo.getPhotoWrapper().imageFile);
    ////
    ////         final TIFFDecodeParam param = null;
    ////
    ////         final ImageDecoder dec = ImageCodec.createImageDecoder("tiff", s, param);
    ////
    ////         // Which of the multiple images in the TIFF file do we want to load
    ////         // 0 refers to the first, 1 to the second and so on.
    ////         final int imageToLoad = 0;
    ////
    ////         final RenderedImage op = new NullOpImage(
    ////               dec.decodeAsRenderedImage(imageToLoad),
    ////               null,
    ////               OpImage.OP_IO_BOUND,
    ////               null);
    ////
    ////         final BufferedImage img = new BufferedImage(op.getWidth(), op.getHeight(), BufferedImage.TYPE_INT_ARGB);
    //
    //      } else {
    //
    //         return ImageIO.read(photoWrapper.imageFile);
    //      }
    //   }

    /**
     * This is called from the executor when the loading task is starting. It loads an image and
     * puts it into the image cache from where it is fetched when painted.
     * 
     * <pre>
     * 
     * 2 Threads
     * =========
     * 
     * SWT
     * Photo-Image-Loader-1   IMG_1219_10.JPG   load:   1165   resize:   645   save:   110   total:   1920
     * Photo-Image-Loader-0   IMG_1219_9.JPG   load:   1165   resize:   650   save:   110   total:   1925
     * Photo-Image-Loader-1   IMG_1219.JPG   load:   566      resize:   875   save:   60   total:   1501
     * Photo-Image-Loader-0   IMG_1219_2.JPG   load:   835      resize:   326   save:   55   total:   1216
     * Photo-Image-Loader-1   IMG_1219_3.JPG   load:   1150   resize:   625   save:   55   total:   1830
     * Photo-Image-Loader-0   IMG_1219_4.JPG   load:   565      resize:   630   save:   60   total:   1255
     * Photo-Image-Loader-1   IMG_1219_5.JPG   load:   566      resize:   880   save:   60   total:   1506
     * Photo-Image-Loader-0   IMG_1219_6.JPG   load:   845      resize:   341   save:   65   total:   1251
     * Photo-Image-Loader-1   IMG_1219_7.JPG   load:   575      resize:   875   save:   50   total:   1500
     * Photo-Image-Loader-0   IMG_1219_8.JPG   load:   845      resize:   356   save:   45   total:   1246
     *                                     8277         6203      670         15150
     * 
     * 
     * AWT
     * Photo-Image-Loader-1   IMG_1219_9.JPG   load:   1005   resize:   770      save AWT:   25   load SWT:   10   total:   1810
     * Photo-Image-Loader-0   IMG_1219_10.JPG   load:   1015   resize:   1311   save AWT:   145   load SWT:   5   total:   2476
     * Photo-Image-Loader-1   IMG_1219.JPG   load:   931      resize:   755      save AWT:   65   load SWT:   5   total:   1756
     * Photo-Image-Loader-0   IMG_1219_2.JPG   load:   960      resize:   737      save AWT:   30   load SWT:   5   total:   1732
     * Photo-Image-Loader-1   IMG_1219_3.JPG   load:   1340   resize:   700      save AWT:   25   load SWT:   10   total:   2075
     * Photo-Image-Loader-0   IMG_1219_4.JPG   load:   935      resize:   751      save AWT:   25   load SWT:   10   total:   1721
     * Photo-Image-Loader-1   IMG_1219_5.JPG   load:   981      resize:   810      save AWT:   25   load SWT:   5   total:   1821
     * Photo-Image-Loader-0   IMG_1219_6.JPG   load:   970      resize:   821      save AWT:   30   load SWT:   5   total:   1826
     * Photo-Image-Loader-1   IMG_1219_7.JPG   load:   950      resize:   710      save AWT:   25   load SWT:   5   total:   1690
     * Photo-Image-Loader-0   IMG_1219_8.JPG   load:   950      resize:   706      save AWT:   30   load SWT:   5   total:   1691
     *                                     10037         8071            425            65         18598
     * 
     * 1 Thread
     * ========
     * 
     * SWT
     * Photo-Image-Loader-0   IMG_1219_10.JPG   load:   595   resize:   330   save:   70   total:   995
     * Photo-Image-Loader-0   IMG_1219.JPG   load:   561   resize:   325   save:   80   total:   966
     * Photo-Image-Loader-0   IMG_1219_2.JPG   load:   560   resize:   330   save:   50   total:   940
     * Photo-Image-Loader-0   IMG_1219_3.JPG   load:   561   resize:   325   save:   45   total:   931
     * Photo-Image-Loader-0   IMG_1219_4.JPG   load:   570   resize:   325   save:   50   total:   945
     * Photo-Image-Loader-0   IMG_1219_5.JPG   load:   570   resize:   340   save:   50   total:   960
     * Photo-Image-Loader-0   IMG_1219_6.JPG   load:   575   resize:   330   save:   45   total:   950
     * Photo-Image-Loader-0   IMG_1219_7.JPG   load:   560   resize:   335   save:   50   total:   945
     * Photo-Image-Loader-0   IMG_1219_8.JPG   load:   565   resize:   330   save:   45   total:   940
     * Photo-Image-Loader-0   IMG_1219_9.JPG   load:   565   resize:   330   save:   45   total:   940
     *                                     5682      3300      530         9512
     * 
     * AWT
     * Photo-Image-Loader-0   IMG_1219.JPG   load:   1115   resize:   790   save AWT:   45   load SWT:   5   total:   1955
     * Photo-Image-Loader-0   IMG_1219_2.JPG   load:   1070   resize:   695   save AWT:   30   load SWT:   5   total:   1800
     * Photo-Image-Loader-0   IMG_1219_3.JPG   load:   1035   resize:   695   save AWT:   25   load SWT:   5   total:   1760
     * Photo-Image-Loader-0   IMG_1219_4.JPG   load:   1040   resize:   695   save AWT:   25   load SWT:   5   total:   1765
     * Photo-Image-Loader-0   IMG_1219_5.JPG   load:   1040   resize:   695   save AWT:   25   load SWT:   110   total:   1870
     * Photo-Image-Loader-0   IMG_1219_6.JPG   load:   1050   resize:   690   save AWT:   25   load SWT:   5   total:   1770
     * Photo-Image-Loader-0   IMG_1219_7.JPG   load:   1035   resize:   690   save AWT:   145   load SWT:   5   total:   1875
     * Photo-Image-Loader-0   IMG_1219_8.JPG   load:   1032   resize:   700   save AWT:   20   load SWT:   10   total:   1762
     * Photo-Image-Loader-0   IMG_1219_9.JPG   load:   1030   resize:   700   save AWT:   25   load SWT:   5   total:   1760
     * Photo-Image-Loader-0   IMG_1219_10.JPG   load:   1032   resize:   700   save AWT:   25   load SWT:   5   total:   1762
     *                                     10479         7050         390            160         18079
     * 
     * </pre>
     * 
     * @param waitingqueueoriginal
     * @param waitingqueueexif
     * @return Returns <code>true</code> when image should be loaded in HQ.
     */
    public boolean loadImageThumb(final LinkedBlockingDeque<PhotoImageLoader> waitingQueueOriginal) {

        /*
         * wait until original images are loaded
         */
        try {
            while (waitingQueueOriginal.size() > 0) {
                Thread.sleep(PhotoLoadManager.DELAY_TO_CHECK_WAITING_QUEUE);
            }
        } catch (final InterruptedException e) {
            // should not happen, I hope so
        }

        boolean isLoadedImageInRequestedQuality = false;
        Image loadedExifImage = null;
        String imageKey = null;
        boolean isLoadingError = false;

        boolean isHQRequired = false;

        try {

            // 1. get image with the requested quality from the image store
            final Image storeImage = loadImageFromStore(_requestedImageQuality);
            if (storeImage != null) {

                isLoadedImageInRequestedQuality = true;

                imageKey = _requestedImageKey;
                loadedExifImage = storeImage;

            } else {

                // 2. get image from thumbnail image in the EXIF data

                //  debug (delay) image loading
                //            Thread.sleep(500);

                final IPath storeThumbImageFilePath = ThumbnailStore.getStoreImagePath(_photo, ImageQuality.THUMB);

                final Image exifThumbnail = loadImageFromEXIFThumbnail(storeThumbImageFilePath);
                if (exifThumbnail != null) {

                    // EXIF image is available

                    isLoadedImageInRequestedQuality = _requestedImageQuality == ImageQuality.THUMB;

                    imageKey = _photo.getImageKey(ImageQuality.THUMB);
                    loadedExifImage = exifThumbnail;
                }
            }

        } catch (final Exception e) {

            setStateLoadingError();

            isLoadingError = true;

        } finally {

            disposeTrackedImages();

            final boolean isImageLoaded = loadedExifImage != null;

            /*
             * keep image in cache
             */
            if (isImageLoaded) {

                final String originalImagePathName = _photo.imageFilePathName;

                // ensure metadata are loaded
                _photo.getImageMetaData();

                int imageWidth = _photo.getPhotoImageWidth();
                int imageHeight = _photo.getPhotoImageHeight();

                // check if width is set
                if (imageWidth == Integer.MIN_VALUE) {

                    // photo image width/height is not set from metadata, set it from the image

                    final Rectangle imageBounds = loadedExifImage.getBounds();
                    imageWidth = imageBounds.width;
                    imageHeight = imageBounds.height;

                    // update dimension
                    updateImageSize(imageWidth, imageHeight, false);
                }

                PhotoImageCache.putImage(imageKey, loadedExifImage, originalImagePathName);
            }

            /*
             * update loading state
             */
            if (isLoadedImageInRequestedQuality) {

                // image is loaded with requested quality, reset image state

                setStateUndefined();

            } else {

                // load image with higher quality

                isHQRequired = true;
            }

            // show in the UI, that meta data are loaded, loading message is displayed with another color
            final boolean isUpdateUI = _photo.getImageMetaDataRaw() != null;

            // display image in the loading callback
            _loadCallBack.callBackImageIsLoaded(isUpdateUI || isImageLoaded || isLoadingError);
        }

        return isHQRequired;
    }

    private void setStateLoadingError() {

        // prevent loading the image again
        _photo.setLoadingState(PhotoLoadingState.IMAGE_IS_INVALID, _requestedImageQuality);

        PhotoLoadManager.putPhotoInLoadingErrorMap(_photo.imageFilePathName);
    }

    private void setStateUndefined() {

        // set state to undefined that it will be loaded again when image is visible and not in the cache
        _photo.setLoadingState(PhotoLoadingState.UNDEFINED, _requestedImageQuality);
    }

    @Override
    public String toString() {
        return "PhotoImageLoaderItem [" //$NON-NLS-1$
                + ("_filePathName=" + _requestedImageKey + "{)}, ") //$NON-NLS-1$ //$NON-NLS-2$
                + ("imageQuality=" + _requestedImageQuality + "{)}, ") //$NON-NLS-1$ //$NON-NLS-2$
                + ("photo=" + _photo) //$NON-NLS-1$
                + "]"; //$NON-NLS-1$
    }

    /**
     * Crop thumb image when it has a different ratio than the original image. This will remove the
     * black margins which are set in the thumb image depending on the image ratio.
     * 
     * @param thumbImage
     * @param width
     * @param height
     * @return
     */
    private BufferedImage transformImageCrop(final BufferedImage thumbImage) {

        final int thumbWidth = thumbImage.getWidth();
        final int thumbHeight = thumbImage.getHeight();
        final int photoImageWidth = _photo.getAvailableImageWidth();
        final int photoImageHeight = _photo.getAvailableImageHeight();

        final double thumbRatio = (double) thumbWidth / thumbHeight;
        double photoRatio;
        boolean isRotate = false;

        if (photoImageWidth == Integer.MIN_VALUE) {

            return thumbImage;

        } else {

            photoRatio = (double) photoImageWidth / photoImageHeight;

            if (thumbRatio < 1.0 && photoRatio > 1.0 || thumbRatio > 1.0 && photoRatio < 1.0) {

                /*
                 * thumb and photo have total different ratios, this can happen when an image is
                 * resized or rotated and the thumb image was not adjusted
                 */

                photoRatio = 1.0 / photoRatio;

                /*
                 * rotate image to the photo orientation, it's rotated 90 degree to the right but it
                 * cannot be determined which is the correct direction
                 */
                if (_photo.getOrientation() <= 1) {
                    // rotate it only when rotation is not set in the exif data
                    isRotate = true;
                }
            }
        }

        final int thumbRatioTruncated = (int) (thumbRatio * 100);
        final int photoRatioTruncated = (int) (photoRatio * 100);

        if (thumbRatioTruncated == photoRatioTruncated) {
            // ratio is the same
            return thumbImage;
        }

        int cropX;
        int cropY;
        int cropWidth;
        int cropHeight;

        if (thumbRatioTruncated < photoRatioTruncated) {

            // thumb height is smaller than photo height

            cropWidth = thumbWidth;
            cropHeight = (int) (thumbWidth / photoRatio);

            cropX = 0;

            cropY = thumbHeight - cropHeight;
            cropY /= 2;

        } else {

            // thumb width is smaller than photo width

            cropWidth = (int) (thumbHeight * photoRatio);
            cropHeight = thumbHeight;

            cropX = thumbWidth - cropWidth;
            cropX /= 2;

            cropY = 0;
        }

        final BufferedImage croppedImage = Scalr.crop(thumbImage, cropX, cropY, cropWidth, cropHeight);
        _trackedAWTImages.add(croppedImage);

        BufferedImage rotatedImage = croppedImage;
        if (isRotate) {

            rotatedImage = Scalr.rotate(croppedImage, Rotation.CW_90);
            _trackedAWTImages.add(rotatedImage);
        }

        return rotatedImage;
    }

    /**
     * @param scaledImage
     * @return Returns rotated image when orientations is not default
     */
    private BufferedImage transformImageRotate(final BufferedImage scaledImage) {

        BufferedImage rotatedImage = scaledImage;

        final int orientation = _photo.getOrientation();

        if (orientation > 1) {

            // see here http://www.impulseadventure.com/photo/exif-orientation.html

            Rotation correction = null;
            if (orientation == 8) {
                correction = Rotation.CW_270;
            } else if (orientation == 3) {
                correction = Rotation.CW_180;
            } else if (orientation == 6) {
                correction = Rotation.CW_90;
            }

            rotatedImage = Scalr.rotate(scaledImage, correction);
            _trackedAWTImages.add(rotatedImage);
        }

        return rotatedImage;
    }

    /**
     * @param isThumbSize
     * @param loadedImage
     */
    private void updateImageSize(final int imageWidth, final int imageHeight, final boolean isOriginalSize) {

        if (isOriginalSize) {

            _photo.setPhotoDimension(imageWidth, imageHeight);

            // update cached image size

            PhotoImageCache.setImageSize(_photo, imageWidth, imageHeight);

        } else {

            _photo.setThumbDimension(imageWidth, imageHeight);
        }
    }

}