/******************************************************************************* * Copyright 2012 Geoscience Australia * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package au.gov.ga.earthsci.worldwind.common.layers.delegate.reader; import java.awt.AlphaComposite; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import au.gov.ga.earthsci.worldwind.common.util.URLUtil; /** * Helper class used for retrieving a mask png file relative to an image tile. * * @author Michael de Hoog (michael.dehoog@ga.gov.au) */ public class MaskHelper { /** * Wrapper around a function used to handling reading mask files from within * a zip file. */ public static interface MaskInsideZipDelegate { /** * Read the given {@link ZipEntry} from the given {@link ZipInputStream} * . This function should check if the entry matches a mask entry, and * if so, reads the mask to a local image. It should also read any other * entries within the zip file that are not mask images, so that the zip * entry doesn't have to be read twice. * * @param entry * {@link ZipEntry} to read. <code>zis</code> is positioned * at the beginning of this entry. * @param zis * {@link ZipInputStream} containing the zip entry. * @throws IOException * If reading the entry fails. */ void readEntry(ZipEntry entry, ZipInputStream zis) throws IOException; } /** * Generate a URL pointing to the mask image for the given image url. * * @param url * URL of the image to find the mask image for. * @param upDirectoryCount * How many directories to move up to find the mask directory. * For example, if the image file was stored in * <code>dataset/level/row/tile.jpg</code>, and the mask was * stored in <code>mask/level/row/tile.png</code>, the number of * directories to move up would be 3. * @param delegate * Delegate to call if the image/mask is containing within a zip * file. This should handle reading the image/mask from the * associated zip entry, as if this delegate is called, this * function returns null. * @return URL pointing to the mask image file. It is the caller's * responsibility to check if this file exists. Null is returned if * the image file is within a zip file; instead, the given * <code>delegate</code> is called. */ public static URL getMaskURL(URL url, int upDirectoryCount, MaskInsideZipDelegate delegate) { boolean isZIP = url.toString().toLowerCase().endsWith("zip"); if (isZIP) { try { ZipInputStream zis = new ZipInputStream(url.openStream()); ZipEntry entry; while ((entry = zis.getNextEntry()) != null) { try { delegate.readEntry(entry, zis); } catch (IOException e) { //ignore (read next ZipEntry) } } } catch (IOException e) { e.printStackTrace(); } return null; } try { if (url.getProtocol().equalsIgnoreCase("jar") || url.getProtocol().equalsIgnoreCase("zip")) { //if the URL is pointing to an entry within a zip file, then create a //new URL for the mask png file inside another zip file (mask.zip) String urlString = url.toString(); int indexOfBang = urlString.lastIndexOf('!'); String zipFile = urlString.substring(0, indexOfBang); int lastIndexOfSlash = zipFile.lastIndexOf('/'); String maskFile = zipFile.substring(0, lastIndexOfSlash + 1) + "mask.zip"; String entry = urlString.substring(indexOfBang); int lastIndexOfPeriod = entry.lastIndexOf('.'); entry = entry.substring(0, lastIndexOfPeriod + 1) + "png"; return new URL(maskFile + entry); } File imageFile = URLUtil.urlToFile(url); if (imageFile == null) //probably will never happen, as url should be the file:// protocol return null; //search for a mask file relative to the image file File maskFile = getMaskFile(imageFile, upDirectoryCount); return maskFile.toURI().toURL(); } catch (MalformedURLException e) { return null; } } /** * Create a File pointing to a 'mask' directory relative to the imageFile * passed. The function moves up {@code upDirectoryCount} parent * directories, replaces the directory with 'mask', and then moves back down * the directories and file again. * * @param imageFile * File for which to find a mask * @return */ protected static File getMaskFile(File imageFile, int upDirectoryCount) { String[] directories = new String[upDirectoryCount]; File parent = imageFile.getParentFile(); for (int i = upDirectoryCount - 1; i >= 0 && parent != null; i--) { directories[i] = parent.getName(); parent = parent.getParentFile(); } if (upDirectoryCount > 0) { parent = new File(parent, "mask"); for (int i = 1; i < upDirectoryCount; i++) { parent = new File(parent, directories[i]); } } int lastIndexOfPeriod = imageFile.getName().lastIndexOf('.'); String filename = imageFile.getName().substring(0, lastIndexOfPeriod); return new File(parent, filename + ".png"); } /** * Add the alpha channel of mask to image, and return the composed image. * * @param image * @param mask * @return image masked by mask */ public static BufferedImage compose(BufferedImage image, BufferedImage mask) { Graphics2D g2d = mask.createGraphics(); g2d.setComposite(AlphaComposite.SrcIn); g2d.drawImage(image, 0, 0, null); g2d.dispose(); return mask; } }