/******************************************************************************* * 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.common.texturegeneration; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.List; import java.util.Scanner; import java.util.concurrent.ArrayBlockingQueue; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.imageio.ImageIO; import jsettlers.graphics.image.Image; import jsettlers.graphics.image.ImageDataPrivider; import jsettlers.graphics.image.SettlerImage; import jsettlers.graphics.image.SingleImage; import jsettlers.graphics.reader.AdvancedDatFileReader; import jsettlers.graphics.reader.DatFileType; /** * This class lets you generate a texture that can be understood by the graphics module. It generates the .texture file. * * @author michael */ public final class TextureGenerator { private static final Pattern ORIGINAL_SETTLER = Pattern.compile("original_\\d+_SETTLER_\\d+_\\d+"); private static class ImageData { ImageDataPrivider data = null; ImageDataPrivider torso = null; public String name; } private class LoadImage implements Runnable { @Override public void run() { try { while (true) { long start = System.currentTimeMillis(); String toLoad = imagesToLoad.take(); ImageData data = addIdToTexture(toLoad); imagesToStore.put(data); System.out.println("Time for loading " + data.name + ": " + (System.currentTimeMillis() - start)); } } catch (InterruptedException e) { } } } private class StoreImage implements Runnable { @Override public void run() { try { while (true) { long start = System.currentTimeMillis(); ImageData data = imagesToStore.take(); storeImageData(data); System.out.println("Time for storing " + data.name + ": " + (System.currentTimeMillis() - start)); } } catch (InterruptedException e) { } } } private static final int QUEUE_LENGTH = 32; private static final int THREADS = 8; private final File rawDirectory; private final File outDirectory; private final TextureIndex textureIndex; private final ArrayBlockingQueue<ImageData> imagesToStore = new ArrayBlockingQueue<>( QUEUE_LENGTH); private final ArrayBlockingQueue<String> imagesToLoad = new ArrayBlockingQueue<>( QUEUE_LENGTH); private final Object pipelineMutex = new Object(); private int imagesInPipeline; private Thread[] started; public TextureGenerator(TextureIndex textureIndex, File rawDirectory, File outDirectory) { this.textureIndex = textureIndex; this.rawDirectory = rawDirectory; this.outDirectory = outDirectory; } /** * Start all threads. FIXME: Leaks threads. */ public void start() { started = new Thread[THREADS * 2]; for (int i = 0; i < THREADS; i++) { started[i] = new Thread(new LoadImage()); started[i].start(); } for (int i = 0; i < THREADS; i++) { started[i + THREADS] = new Thread(new StoreImage()); started[i + THREADS].start(); } } /** * Wait for completion on all threads. * * @throws InterruptedException */ public void join() { try { synchronized (pipelineMutex) { while (imagesInPipeline > 0) { pipelineMutex.wait(); } } for (int i = 0; i < started.length; i++) { started[i].interrupt(); } } catch (InterruptedException e) { } } /** * Start compiling a new file. Might block some time. * * @param list */ public void addTexturesByName(List<String> list) { for (String name : list) { try { imagesToLoad.put(name); synchronized (pipelineMutex) { imagesInPipeline++; } } catch (InterruptedException e) { } } } private ImageData addIdToTexture(String name) { Matcher matcher = ORIGINAL_SETTLER.matcher(name); ImageData imageData = new ImageData(); imageData.name = name; // open original image files if (matcher.matches()) { File datfile = null; // TODO: Load dat file matcher.group(1) AdvancedDatFileReader reader = new AdvancedDatFileReader(datfile, DatFileType.RGB555); Image image = reader.getSettlers() .get(Integer.parseInt(matcher.group(2))) .getImageSafe(Integer.parseInt(matcher.group(3))); if (image instanceof SingleImage) { imageData.data = (SingleImage) image; } if (image instanceof SettlerImage) { imageData.torso = (SingleImage) ((SettlerImage) image) .getTorso(); } } else { imageData.data = getImage(name); imageData.torso = getImage(name + ".t"); } if (imageData.data == null) { System.err.println("WATNING: loading image " + name + ": No image file found."); } return imageData; } private void storeImageData(ImageData imageData) { storeImage(imageData.name, imageData.data, imageData.torso != null); if (imageData.torso != null) { storeImage(imageData.name, imageData.torso, false); } synchronized (pipelineMutex) { imagesInPipeline--; pipelineMutex.notifyAll(); } } private void storeImage(String name, ImageDataPrivider data, boolean hasTorso) { try { if (data != null) { int texture = textureIndex.getNextTextureIndex(); TexturePosition position = addAsNewImage(data, texture); textureIndex.registerTexture(name, texture, data.getOffsetX(), data.getOffsetY(), data.getWidth(), data.getHeight(), hasTorso, position); } } catch (Throwable t) { System.err.println("WARNING: Problem writing image " + name + ". Problem was: " + t.getMessage()); } } // This is slow. private TexturePosition addAsNewImage(ImageDataPrivider data, int texture) throws IOException { int size = getNextPOT(Math.max(data.getWidth(), data.getHeight())); TextureFile file = new TextureFile(new File(outDirectory, texture + ""), size, size); TexturePosition position = file.addImage(data.getData(), data.getWidth()); file.write(); return position; } private static int getNextPOT(int height) { int i = 2; while (i < height) { i *= 2; } return i; } private ImageDataPrivider getImage(String id) { try { File imageFile = new File(rawDirectory, id + ".png"); int[] offsets = getOffsets(id); BufferedImage image = ImageIO.read(imageFile); return new ProvidedImage(image, offsets); } catch (Throwable t) { System.err.println("WARNING: Problem reading image " + id + ". Problem was: " + t.getMessage()); return null; } } private int[] getOffsets(String id) { int[] offsets = new int[2]; File offset = new File(rawDirectory, id + ".png.offset"); try (Scanner in = new Scanner(offset)) { offsets[0] = in.nextInt(); in.skip("\\s+"); offsets[1] = in.nextInt(); in.close(); return offsets; } catch (Throwable t) { System.err.println("WARNING: Problem reading offsets for " + id + ", assuming (0,0). Problem was: " + t.getMessage()); return new int[] { 0, 0 }; } } }