/******************************************************************************* * Copyright (c) 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *******************************************************************************/ package jsettlers.graphics.map.draw; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Hashtable; import java.util.List; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import jsettlers.common.images.DirectImageLink; import jsettlers.common.images.EImageLinkType; import jsettlers.common.images.ImageLink; import jsettlers.common.images.OriginalImageLink; //import jsettlers.common.images.TextureMap; import jsettlers.graphics.image.GuiImage; import jsettlers.graphics.image.Image; import jsettlers.graphics.image.ImageIndexFile; import jsettlers.graphics.image.LandscapeImage; import jsettlers.graphics.image.NullImage; import jsettlers.graphics.image.SingleImage; import jsettlers.graphics.reader.AdvancedDatFileReader; import jsettlers.graphics.reader.DatFileSet; import jsettlers.graphics.reader.DatFileType; import jsettlers.graphics.reader.SequenceList; import jsettlers.graphics.sequence.ArraySequence; import jsettlers.graphics.sequence.Sequence; /** * This is the main image provider. It provides access to all images. * <p> * Settlers supports two image modes, one rgb mode (555 bits) and one rgb mode (565 bits). * * @author michael */ public final class ImageProvider { private static final String FILE_PREFIX = "siedler3_"; private static final int LAST_SEQUENCE_NUMBER = 2; private static final List<Integer> HIGHRES_IMAGE_FILE_NUMBERS = Arrays.asList(3, 14); private final Queue<GLPreloadTask> tasks = new ConcurrentLinkedQueue<GLPreloadTask>(); private ImageIndexFile indexFile = null; private static final DatFileSet EMPTY_SET = new DatFileSet() { @Override public SequenceList<Image> getSettlers() { return new SequenceList<Image>() { @Override public Sequence<Image> get(int index) { return null; } @Override public int size() { return 0; } }; } @Override public Sequence<LandscapeImage> getLandscapes() { return new ArraySequence<LandscapeImage>(new LandscapeImage[0]); } @Override public Sequence<GuiImage> getGuis() { return new ArraySequence<GuiImage>(new GuiImage[0]); } }; private static ImageProvider instance; private final Hashtable<Integer, AdvancedDatFileReader> readers = new Hashtable<Integer, AdvancedDatFileReader>(); /** * The lookup paths for the dat files. */ private final List<File> lookupPaths = new ArrayList<File>(); private ImageProvider() { } /** * Gets an instance of an image provider * * @return The provider */ public static ImageProvider getInstance() { if (instance == null) { instance = new ImageProvider(); } return instance; } /** * Adds a new path to look for dat files. * * @param path * The directory. It may not exist, but must not be null. * @return this */ public ImageProvider addLookupPath(File path) { this.lookupPaths.add(path); return this; } /** * Tries to get a file content. * * @param file * The file number to search for. * @return The content as set or <code> null </code> */ public synchronized AdvancedDatFileReader getFileReader(int file) { Integer integer = Integer.valueOf(file); AdvancedDatFileReader set = this.readers.get(integer); if (set == null) { set = createFileReader(file); if (set != null) { this.readers.put(integer, set); } } return set; } public synchronized DatFileSet getFileSet(int file) { AdvancedDatFileReader set = getFileReader(file); if (set != null) { return set; } else { return EMPTY_SET; } } /** * Gets an image by a link. * * @param link * The link that describes the image * @return The image or a null image. */ public Image getImage(ImageLink link) { return getImage(link, -1, -1); } /** * Gets the highest resolution image that fits the given size. * * @param link * The link that describes the image * @param width * The width the image should have (at least). * @param height * The height the image should have (at least). * @return The image or a null image. */ public Image getImage(ImageLink link, float width, float height) { if (link == null) { return NullImage.getInstance(); } else if (link instanceof DirectImageLink) { return getDirectImage((DirectImageLink) link); } else { OriginalImageLink olink = (OriginalImageLink) link; if (olink.getType() == EImageLinkType.LANDSCAPE) { return getLandscapeImage(olink.getFile(), olink.getSequence()); } else { return getDetailedImage(olink, width, height); } } } /** * Returns a GUI or SETTLER type image and if available a higher resolution version. This is also based on whether the image's dimensions in * pixels will fit into both the specified width and height. This is so that an image is always scaled up as downsizing an image can introduce * artifacts and it would be wasteful to be calculating the translation of excess pixels from a large image to a smaller one. However should the * smallest image be oversized it will still be returned. */ private Image getDetailedImage(OriginalImageLink link, float width, float height) { Image image = getSequencedImage(link, 0); if (!HIGHRES_IMAGE_FILE_NUMBERS.contains(link.getFile())) { // Higher resolution images are only available in some files. return image; } int sequenceNumber = 0; Image higherResImg = image; while (higherResImg.getWidth() < width && higherResImg.getHeight() < height) { image = higherResImg; if (++sequenceNumber > LAST_SEQUENCE_NUMBER) { break; } higherResImg = getSequencedImage(link, sequenceNumber); } return image; } /** * Expects a valid sequence number. * * @param link * @param sequenceNumber * must be an integer from 0 to 2. * @return the image matching the specified indexes. */ private Image getSequencedImage(OriginalImageLink link, int sequenceNumber) { if (link.getType() == EImageLinkType.SETTLER) { return getSettlerSequence(link.getFile(), link.getSequence()).getImageSafe(link.getImage() + sequenceNumber); } else { return getGuiImage(link.getFile(), link.getSequence() + sequenceNumber); } } private Image getDirectImage(DirectImageLink link) { if (indexFile == null) { indexFile = new ImageIndexFile(); } int index = 0; //TextureMap.getIndex(link.getName()); return indexFile.getImage(index); } /** * Gets a landscape texture. * * @param file * The file number it is in. * @param seqnumber * It's sequence number. * @return The image, or an empty image. */ private SingleImage getLandscapeImage(int file, int seqnumber) { DatFileSet set = getFileSet(file); if (set != null) { Sequence<LandscapeImage> landscapes = set.getLandscapes(); if (seqnumber < landscapes.length()) { return (SingleImage) landscapes.getImageSafe(seqnumber); } } return NullImage.getInstance(); } /** * Gets a given gui image. * * @param file * The file the image is in. * @param seqnumber * The image number. * @return The image. */ public SingleImage getGuiImage(int file, int seqnumber) { DatFileSet set = getFileSet(file); if (set != null) { return (SingleImage) set.getGuis().getImageSafe(seqnumber); } else { return NullImage.getInstance(); } } /** * Gets an settler sequence. * * @param file * The file of the sequence. * @param seqnumber * The number of the sequence in the file. * @return The settler sequence. */ public Sequence<? extends Image> getSettlerSequence(int file, int seqnumber) { DatFileSet set = getFileSet(file); if (set != null && set.getSettlers().size() > seqnumber) { return set.getSettlers().get(seqnumber); } else { return ArraySequence.getNullSequence(); } } /** * marks all loaded images as invalid. TODO: ensure that they get deleted */ public void invalidateAll() { readers.clear(); Background.invalidateTexture(); } private File findFileInPaths(String fileName) { for (File path : this.lookupPaths) { File searched = new File(path, fileName); if (searched.isFile() && searched.canRead()) { return searched; } } return null; } private AdvancedDatFileReader createFileReader(int fileIndex) { String numberString = String.format("%02d", fileIndex); for (DatFileType type : DatFileType.values()) { String fileName = FILE_PREFIX + numberString + type.getFileSuffix(); File file = findFileInPaths(fileName); if (file != null) { return new AdvancedDatFileReader(file, type); } } System.err.println("Could not find/load graphic file " + numberString); return null; } /** * Starts preloading the images, if lookup paths have been set. * * @return */ public Thread startPreloading() { if (!lookupPaths.isEmpty()) { Thread thread = new Thread(new ImagePreloadTask(), "image preloader"); thread.start(); return thread; } else { return null; } } /** * Adds a preload task that is executed on the OpenGl thread with a opengl context. * <p> * The task may never be executed. */ public void addPreloadTask(GLPreloadTask task) { tasks.add(task); } }