public static final javafx.scene.text.Font IMAGE_VIEW__FONT = javafx.scene.text.Font.loadFont(
		LocalDbImageView.class.getResource("/style/fonts/FjallaOne-Regular.ttf").toExternalForm(),16); public static final javafx.scene.paint.Color IMAGE_VIEW__FONT_COLOR = javafx.scene.paint.Color.color(0.3,0.3,0.3); public static final javafx.scene.paint.Color IMAGE_VIEW__NON_SELECTED_COLOR = javafx.scene.paint.Color.color(0.8,0.8,0.8); public static final javafx.scene.paint.Color IMAGE_VIEW__SELECTED_COLOR = javafx.scene.paint.Color.web("#c33"); //color(0.3,0.8,0.3); public static final String PREVIEW_FORMAT = "jpg"; public interface CryptographyService extends Disposable {
	//************* Base crypto *************
    byte[] encrypt(byte[] open);
    byte[] decrypt(byte[] encrypted);
    byte[] hash(byte[] data);
    byte[] hash(byte[] data, int iteration);
    byte[] getAuthData();

	//************* File name generator *************
	String getNameForMainDb();
	String getNameForMetadataDb();
	String getNameForCacheDb();
} public interface LocalStorageService extends Disposable {
    byte[] getCacheItem(byte[] hash, int w, int h);
    void storeCacheItem(byte[] hash, byte[] data, int w, int h);
    void resetCache();
    void resetCacheElement(byte[] hash, int w, int h);

    byte[] getLocalDBItem(byte[] hash);
    void importAllLocalDBItems(Collection<Path> files, Collection<String> tags, WaitInformer informer);
    void importProcessStop();
	boolean saveImageToExchangeFolder(ImageEntity currentImage, String exchangeFolder);
} public interface UseServices {
	InitService initService = new InitService();

	default <T> T getService(Class<T> clazz) {
		if (initService.getServices() == null || !initService.getServices().containsKey(clazz)) {
			throw new IllegalStateException("Class \"" + clazz + "\" not found in list of services");
		}
		return (T) initService.getServices().get(clazz);
	}

	default GlobalConfig getConfig() {
		return initService.getGlobalConfig();
	}

	default void init(String password) {
		initService.setPassword(password);
		initService.init();
	}

	default void dispose() {
		initService.dispose();
	}
} public class ImageUtils {
	private static final Scalr.Method quality = Scalr.Method.SPEED;
	private static final Color COLOR_WHITE = new Color(255, 255, 255);

	public static BufferedImage resizeImage(BufferedImage image2, long sizeW, long sizeH, boolean crop) {
		final int
				w_size = image2.getWidth(null),
				h_size = image2.getHeight(null);

		final BufferedImage srcImage = new BufferedImage(w_size, h_size, BufferedImage.TYPE_INT_RGB);
		final Graphics2D g2d = srcImage.createGraphics();
		g2d.setBackground(COLOR_WHITE);
		g2d.setColor(COLOR_WHITE);
		g2d.fillRect(0, 0, w_size, h_size);
		g2d.drawImage(image2, 0, 0, null);
		g2d.dispose();

		final BufferedImage out_img, crop_img;
		if (crop) {
			if (srcImage.getWidth() > srcImage.getHeight()) {
				final double
						in_k  = ((double) srcImage.getWidth()) / ((double) srcImage.getHeight()),
						out_k = ((double) sizeW) / ((double) sizeH);

				out_img = Scalr.resize(srcImage, quality, Scalr.Mode.FIT_TO_HEIGHT, (int) ((in_k < out_k) ? ((int)(sizeH * out_k)) : (sizeH)), Scalr.OP_ANTIALIAS);
				final long
						out_x = (out_img.getWidth() - sizeW) / 2,
						out_y = (out_img.getHeight() - sizeH) / 2;

				crop_img = Scalr.crop(out_img, (int) out_x, (int) out_y, (int) sizeW, (int) sizeH, Scalr.OP_ANTIALIAS);
			} else {
				final double
						in_k  = ((double) srcImage.getHeight()) / ((double) srcImage.getWidth()),
						out_k = ((double) sizeH) / ((double) sizeW);

				out_img = Scalr.resize(srcImage, quality, Scalr.Mode.FIT_TO_WIDTH, (int) ((in_k < out_k) ? ((int)(sizeW * out_k)) : (sizeW)), Scalr.OP_ANTIALIAS);
				final long
						out_x = (out_img.getWidth() - sizeW) / 2,
						out_y = (out_img.getHeight() - sizeH) / 2;

				crop_img = Scalr.crop(out_img, (int) out_x, (int) out_y, (int) sizeW, (int) sizeH, Scalr.OP_ANTIALIAS);
			}

			return crop_img;
		} else {
			out_img = Scalr.resize(srcImage,quality, Scalr.Mode.AUTOMATIC, (int) sizeW, (int) sizeH, Scalr.OP_ANTIALIAS);
			return out_img;
		}
	}
} public class UiUtils {

	public static void pleaseWait(Canvas canvas, Paint color, Paint fontColor, Font font) {
		final GraphicsContext context = canvas.getGraphicsContext2D();
		context.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
		context.setLineWidth(3.0);
		context.setStroke(color);
		context.strokeRect(0, 0, canvas.getWidth(), canvas.getHeight());
		context.setFill(fontColor);
		context.setTextAlign(TextAlignment.CENTER);
		context.setTextBaseline(VPos.CENTER);
		context.setFont(font);
		context.fillText("Please, wait...", canvas.getWidth()/2, canvas.getHeight()/2);
	}

	public static void drawBinaryImage(Canvas canvas, byte[] data, int w, int h, Paint color, boolean selected) {
		final Image resultImage = new Image(new ByteArrayInputStream(data));
		final GraphicsContext context = canvas.getGraphicsContext2D();
		context.clearRect(0, 0, w, h);
		context.drawImage(resultImage, 0, 0);
		context.setLineWidth(selected ? 9.0 : 1.5);
		context.setStroke(color);
		context.strokeRect(0, 0, w, h);
	}

	public static void clearCanvas(Canvas canvas) {
		final GraphicsContext context = canvas.getGraphicsContext2D();
		context.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
	}

} public abstract class QueuedWorker<T> {
    private final LinkedBlockingDeque<T> queue;
    private final ExecutorService executor;

    private final AtomicInteger counter = new AtomicInteger(0);

    public LinkedBlockingDeque<T> getQueue() {
        return queue;
    }

    public AtomicInteger getCounter() {
        return counter;
    }

    private final class Worker implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    final T t = getQueue().pollFirst(9999, TimeUnit.DAYS);
                    if (t != null) {
                        threadEvent(t);
                    }
                    if (getCounter().decrementAndGet() == 0) onZeroCount(t);
                } catch (InterruptedException e) {
                    //System.out.println("InterruptedException " + Thread.currentThread().getName());
                    return;
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public abstract void threadEvent(T t);
    public void onZeroCount(T t) {}

    public QueuedWorker(int threads, int queueSize) {
        queue = new LinkedBlockingDeque<>(queueSize);
        executor = Executors.newFixedThreadPool(threads);
        for (int i=0; i<threads; i++) {
            final Worker worker = new Worker();
            final Thread thread = new Thread(worker, "worker" + i);
            executor.submit(thread);
        }
    }

    public QueuedWorker(int queueSize) {
        queue = new LinkedBlockingDeque<>(queueSize);

        int threads = Runtime.getRuntime().availableProcessors();
        if (threads <= 0) throw new IllegalStateException("cannot get CPUs count");

        executor = Executors.newFixedThreadPool(threads);
        for (int i=0; i<threads; i++) {
            final Worker worker = new Worker();
            final Thread thread = new Thread(worker, "worker"+i);
            executor.submit(thread);
        }
    }

    public QueuedWorker() {
        queue = new LinkedBlockingDeque<>();

        int threads = Runtime.getRuntime().availableProcessors();
        if (threads <= 0) throw new IllegalStateException("cannot get CPUs count");

        executor = Executors.newFixedThreadPool(threads);
        for (int i=0; i<threads; i++) {
            final Worker worker = new Worker();
            final Thread thread = new Thread(worker, "worker"+i);
            executor.submit(thread);
        }
    }

    public void pushTask(T t) {
        try {
            getCounter().incrementAndGet();
            getQueue().putFirst(t);
        } catch (InterruptedException e) { }
    }

    public void pushTask(T t, int size) {
        try {
            getCounter().incrementAndGet();
            getQueue().putFirst(t);
            if (getQueue().size() > size) {
                getQueue().removeLast();
            }
        } catch (InterruptedException e) { }
    }

    public void dispose() {
        executor.shutdownNow();
    }
}
import static model.GlobalConfig.IMAGE_VIEW__FONT;
import static model.GlobalConfig.IMAGE_VIEW__FONT_COLOR;
import static model.GlobalConfig.IMAGE_VIEW__NON_SELECTED_COLOR;
import static model.GlobalConfig.IMAGE_VIEW__SELECTED_COLOR;
import static model.GlobalConfig.PREVIEW_FORMAT;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import javax.imageio.ImageIO;
import javafx.application.Platform;
import javafx.scene.canvas.Canvas;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import proto.CryptographyService;
import proto.LocalStorageService;
import proto.UseServices;
import utils.ImageUtils;
import utils.UiUtils;
import worker.QueuedWorker;
package ui.imageview.localfs;






public abstract class LocalFsImageView extends Canvas implements UseServices {
	private static final QueuedWorker<LocalFsImageViewTask> mainWorkersPool = new QueuedWorker<LocalFsImageViewTask>() {
		private LocalStorageService storageService = null;
		private CryptographyService cryptographyService = null;
		final AtomicBoolean lock = new AtomicBoolean(true);

		@Override
		public void threadEvent(LocalFsImageViewTask localFsImageViewTask) {
			if (localFsImageViewTask == null) return;

			final LocalFsImageView canvas = localFsImageViewTask.getLocalFsImageView();
			if (canvas == null) return;
			canvas.setLastTask(localFsImageViewTask);

			if (localFsImageViewTask.getFile() == null) {
				Platform.runLater(() -> UiUtils.clearCanvas(canvas));
				return;
			}

			if (storageService == null) {
				storageService = canvas.getService(LocalStorageService.class);
			}

			if (cryptographyService == null) {
				cryptographyService = canvas.getService(CryptographyService.class);
			}

			final Double w, h;
			while (true) {
				Double wt = localFsImageViewTask.getLocalFsImageView().getWidth();
				Double ht = localFsImageViewTask.getLocalFsImageView().getHeight();
				if (wt >= canvas.getConfig().getMinImageSize() && ht >= canvas.getConfig().getMinImageSize()) {
					w = wt;
					h = ht;
					break;
				} else {
					Thread.yield();
				}
			}

			final File file = localFsImageViewTask.getFile().toFile();
			final String cacheId = file.getName() + "/:" + file.length();
			final byte[] hash = cryptographyService.hash(cacheId.getBytes());

			final byte[] previewData = storageService.getCacheItem(hash, w.intValue(), h.intValue());
			if (previewData != null) {
				localFsImageViewTask.setCachedImage(previewData);
				lock.set(true);
				Platform.runLater(() -> {
					if (canvas.getCurrentId().get() == localFsImageViewTask.getPrivateId()) {
						UiUtils.drawBinaryImage(canvas, previewData, w.intValue(), h.intValue(),
								(canvas.isSelected() ? IMAGE_VIEW__SELECTED_COLOR : IMAGE_VIEW__NON_SELECTED_COLOR), canvas.isSelected());
					}
					lock.set(false);
				});
				while (lock.get()) Thread.yield();
			} else {
				try {
					final byte[] fullFile = Files.readAllBytes(localFsImageViewTask.getFile());

					final BufferedImage bufferedImageOriginal = ImageIO.read(new ByteArrayInputStream(fullFile));
					final BufferedImage bufferedImageResized = ImageUtils.resizeImage(bufferedImageOriginal, w.longValue(), h.longValue(), true);
					final ByteArrayOutputStream baos = new ByteArrayOutputStream();
					ImageIO.write(bufferedImageResized, PREVIEW_FORMAT, baos);