1 /* 2 * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.javafx.iio; 27 28 import com.sun.javafx.PlatformUtil; 29 import com.sun.javafx.iio.ImageFormatDescription.Signature; 30 import com.sun.javafx.iio.bmp.BMPImageLoaderFactory; 31 import com.sun.javafx.iio.common.ImageTools; 32 import com.sun.javafx.iio.gif.GIFImageLoaderFactory; 33 import com.sun.javafx.iio.ios.IosImageLoaderFactory; 34 import com.sun.javafx.iio.jpeg.JPEGImageLoaderFactory; 35 import com.sun.javafx.iio.png.PNGImageLoaderFactory; 36 import java.io.ByteArrayInputStream; 37 import java.io.EOFException; 38 import java.io.IOException; 39 import java.io.InputStream; 40 import java.io.SequenceInputStream; 41 import java.util.ArrayList; 42 import java.util.HashMap; 43 import java.util.Map.Entry; 44 45 /** 46 * A convenience class for simple image loading. Factories for creating loaders 47 * for image formats must be registered with this class. 48 */ 49 public class ImageStorage { 50 51 /** 52 * An enumeration of supported image types. 53 */ 54 public static enum ImageType { 55 56 /** 57 * An image with a single channel of 8-bit valued gray levels. 58 */ 59 GRAY, 60 /** 61 * An image with with two 8-bit valued channels, one of gray levels, 62 * the other of non-premultiplied opacity, ordered as GAGAGA... 63 */ 64 GRAY_ALPHA, 65 /** 66 * An image with with two 8-bit valued channels, one of gray levels, 67 * the other of premultiplied opacity, ordered as GAGAGA... 68 */ 69 GRAY_ALPHA_PRE, 70 /** 71 * An image with with one 8-bit channel of indexes into a 24-bit 72 * lookup table which maps the indexes to 8-bit RGB components. 73 */ 74 PALETTE, 75 /** 76 * An image with with one 8-bit channel of indexes into a 32-bit 77 * lookup table which maps the indexes to 8-bit RGBA components 78 * wherein the opacity is not-premultiplied. 79 */ 80 PALETTE_ALPHA, 81 /** 82 * An image with with one 8-bit channel of indexes into a 32-bit 83 * lookup table which maps the indexes to 8-bit RGBA components 84 * wherein the opacity is premultiplied. 85 */ 86 PALETTE_ALPHA_PRE, 87 /** 88 * An image with with one 8-bit channel of indexes into a 24-bit 89 * lookup table which maps the indexes to 8-bit RGB components, and 90 * a single transparent index to indicate the location of transparent 91 * pixels. 92 */ 93 PALETTE_TRANS, 94 /** 95 * An image with with three 8-bit valued channels of red, green, and 96 * blue, respectively, ordered as RGBRGBRGB... 97 */ 98 RGB, 99 /** 100 * An image with with four 8-bit valued channels of red, green, blue, 101 * and non-premultiplied opacity, respectively, ordered as 102 * RGBARGBARGBA... 103 */ 104 RGBA, 105 /** 106 * An image with with four 8-bit valued channels of red, green, blue, 107 * and premultiplied opacity, respectively, ordered as 108 * RGBARGBARGBA... 109 */ 110 RGBA_PRE 111 }; 112 /** 113 * A mapping of lower case file extensions to loader factories. 114 */ 115 // private static HashMap<String, ImageLoaderFactory> loaderFactoriesByExtension; 116 /** 117 * A mapping of format signature byte sequences to loader factories. 118 */ 119 private static final HashMap<Signature, ImageLoaderFactory> loaderFactoriesBySignature; 120 private static final ImageLoaderFactory[] loaderFactories; 121 private static final boolean isIOS = PlatformUtil.isIOS(); 122 123 private static int maxSignatureLength; 124 125 static { 126 if (isIOS) { 127 //On iOS we have single factory/ native loader 128 //for all image formats 129 loaderFactories = new ImageLoaderFactory[]{ 130 IosImageLoaderFactory.getInstance() 131 }; 132 } else { 133 loaderFactories = new ImageLoaderFactory[]{ 134 GIFImageLoaderFactory.getInstance(), 135 JPEGImageLoaderFactory.getInstance(), 136 PNGImageLoaderFactory.getInstance(), 137 BMPImageLoaderFactory.getInstance() 138 // Note: append ImageLoadFactory for any new format here. 139 }; 140 } 141 142 // loaderFactoriesByExtension = new HashMap(numExtensions); 143 loaderFactoriesBySignature = new HashMap<Signature, ImageLoaderFactory>(loaderFactories.length); 144 145 for (int i = 0; i < loaderFactories.length; i++) { 146 addImageLoaderFactory(loaderFactories[i]); 147 } 148 } 149 150 public static ImageFormatDescription[] getSupportedDescriptions() { 151 ImageFormatDescription[] formats = new ImageFormatDescription[loaderFactories.length]; 152 for (int i = 0; i < loaderFactories.length; i++) { 153 formats[i] = loaderFactories[i].getFormatDescription(); 154 } 155 return (formats); 156 } 157 158 /** 159 * Returns the number of bands for a raw image of the specified type. 160 * 161 * @param type the type of image 162 * @return the number of bands of a raw image of this type 163 */ 164 public static int getNumBands(ImageType type) { 165 int numBands = -1; 166 switch (type) { 167 case GRAY: 168 case PALETTE: 169 case PALETTE_ALPHA: 170 case PALETTE_ALPHA_PRE: 171 case PALETTE_TRANS: 172 numBands = 1; 173 break; 174 case GRAY_ALPHA: 175 case GRAY_ALPHA_PRE: 176 numBands = 2; 177 break; 178 case RGB: 179 numBands = 3; 180 break; 181 case RGBA: 182 case RGBA_PRE: 183 numBands = 4; 184 break; 185 default: 186 throw new IllegalArgumentException("Unknown ImageType " + type); 187 } 188 return numBands; 189 } 190 191 /** 192 * Registers an image loader factory. The factory replaces any other factory 193 * previously registered for the file extensions (converted to lower case) 194 * and signature indicated by the format description. 195 * 196 * @param factory the factory to register. 197 */ 198 public static void addImageLoaderFactory(ImageLoaderFactory factory) { 199 ImageFormatDescription desc = factory.getFormatDescription(); 200 // String[] extensions = desc.getExtensions(); 201 // for (int j = 0; j < extensions.length; j++) { 202 // loaderFactoriesByExtension.put(extensions[j].toLowerCase(), factory); 203 // } 204 205 for (final Signature signature: desc.getSignatures()) { 206 loaderFactoriesBySignature.put(signature, factory); 207 } 208 209 // invalidate max signature length 210 synchronized (ImageStorage.class) { 211 maxSignatureLength = -1; 212 } 213 } 214 215 /** 216 * Load all images present in the specified stream. The image will be 217 * rescaled according to this algorithm: 218 * 219 * <code><pre> 220 * int finalWidth, finalHeight; // final dimensions 221 * int width, height; // specified maximum dimensions 222 * // Use source dimensions as default values. 223 * if (width <= 0) { 224 * width = sourceWidth; 225 * } 226 * if (height <= 0) { 227 * height = sourceHeight; 228 * } 229 * // If not downscaling reset the dimensions to those of the source. 230 * if (!((width < sourceWidth && height <= sourceHeight) || 231 * (width <= sourceWidth && height < sourceHeight))) { 232 * finalWidth = sourceWidth; 233 * finalHeight = sourceHeight; 234 * } else if(preserveAspectRatio) { 235 * double r = (double) sourceWidth / (double) sourceHeight; 236 * finalHeight = (int) ((width / r < height ? width / r : height) + 0.5); 237 * finalWidth = (int) (r * finalHeight + 0.5); 238 * } else { 239 * finalWidth = width; 240 * finalHeight = height; 241 * } 242 * </pre></code> 243 * 244 * @param input the image data stream. 245 * @param listener a listener to receive notifications about image loading. 246 * @param width the desired width of the image; if non-positive, 247 * the original image width will be used. 248 * @param height the desired height of the image; if non-positive, the 249 * original image height will be used. 250 * @param preserveAspectRatio whether to preserve the width-to-height ratio 251 * of the image. 252 * @param smooth whether to apply smoothing when downsampling. 253 * @return the sequence of all images in the specified source or 254 * <code>null</code> on error. 255 */ 256 public static ImageFrame[] loadAll(InputStream input, ImageLoadListener listener, 257 int width, int height, boolean preserveAspectRatio, 258 float pixelScale, boolean smooth) throws ImageStorageException { 259 ImageLoader loader = null; 260 try { 261 if (isIOS) { 262 // no extension/signature recognition done here, 263 // we always want the iOS native loader 264 loader = IosImageLoaderFactory.getInstance().createImageLoader(input); 265 } else { 266 loader = getLoaderBySignature(input, listener); 267 } 268 } catch (IOException e) { 269 throw new ImageStorageException(e.getMessage(), e); 270 } 271 272 ImageFrame[] images = null; 273 if (loader != null) { 274 images = loadAll(loader, width, height, preserveAspectRatio, pixelScale, smooth); 275 } else { 276 throw new ImageStorageException("No loader for image data"); 277 } 278 279 return images; 280 } 281 282 /** 283 * Load all images present in the specified input. For more details refer to 284 * {@link #loadAll(java.io.InputStream, com.sun.javafx.iio.ImageLoadListener, int, int, boolean, boolean)}. 285 */ 286 public static ImageFrame[] loadAll(String input, ImageLoadListener listener, 287 int width, int height, boolean preserveAspectRatio, 288 float pixelScale, boolean smooth) throws ImageStorageException { 289 290 if (input == null || input.isEmpty()) { 291 throw new ImageStorageException("URL can't be null or empty"); 292 } 293 294 ImageFrame[] images = null; 295 InputStream theStream = null; 296 ImageLoader loader = null; 297 298 try { 299 try { 300 if (pixelScale > 1.9f) { 301 // Use Mac Retina conventions for > 1.9f 302 StringBuilder new_input = new StringBuilder(); 303 final int last_dot_idx = input.lastIndexOf("."); 304 new_input.append(input.substring(0, last_dot_idx)); 305 new_input.append("@2x"); 306 new_input.append(input.substring(last_dot_idx)); 307 try { 308 theStream = ImageTools.createInputStream(new_input.toString()); 309 } catch (IOException e) { 310 } 311 } 312 if (theStream == null) { 313 theStream = ImageTools.createInputStream(input); 314 pixelScale = 1.0f; 315 } 316 317 if (isIOS) { 318 loader = IosImageLoaderFactory.getInstance().createImageLoader(theStream); 319 } else { 320 loader = getLoaderBySignature(theStream, listener); 321 } 322 } catch (IOException e) { 323 throw new ImageStorageException(e.getMessage(), e); 324 } 325 326 if (loader != null) { 327 images = loadAll(loader, width, height, preserveAspectRatio, pixelScale, smooth); 328 } else { 329 throw new ImageStorageException("No loader for image data"); 330 } 331 } finally { 332 try { 333 if (theStream != null) { 334 theStream.close(); 335 } 336 } catch (IOException e) { 337 } 338 } 339 340 return images; 341 } 342 343 private static synchronized int getMaxSignatureLength() { 344 if (maxSignatureLength < 0) { 345 maxSignatureLength = 0; 346 for (final Signature signature: 347 loaderFactoriesBySignature.keySet()) { 348 final int signatureLength = signature.getLength(); 349 if (maxSignatureLength < signatureLength) { 350 maxSignatureLength = signatureLength; 351 } 352 } 353 } 354 355 return maxSignatureLength; 356 } 357 358 private static ImageFrame[] loadAll(ImageLoader loader, 359 int width, int height, boolean preserveAspectRatio, 360 float pixelScale, boolean smooth) throws ImageStorageException { 361 ImageFrame[] images = null; 362 ArrayList<ImageFrame> list = new ArrayList<ImageFrame>(); 363 int imageIndex = 0; 364 ImageFrame image = null; 365 do { 366 try { 367 image = loader.load(imageIndex++, width, height, preserveAspectRatio, smooth); 368 } catch (Exception e) { 369 throw new ImageStorageException(e.getMessage(), e); 370 } 371 if (image != null) { 372 image.setPixelScale(pixelScale); 373 list.add(image); 374 } else { 375 break; 376 } 377 } while (true); 378 int numImages = list.size(); 379 if (numImages > 0) { 380 images = new ImageFrame[numImages]; 381 list.toArray(images); 382 } 383 return images; 384 } 385 386 // private static ImageLoader getLoaderByExtension(String input, ImageLoadListener listener) { 387 // ImageLoader loader = null; 388 // 389 // int dotIndex = input.lastIndexOf("."); 390 // if (dotIndex != -1) { 391 // String extension = input.substring(dotIndex + 1).toLowerCase(); 392 // Set extensions = loaderFactoriesByExtension.keySet(); 393 // if (extensions.contains(extension)) { 394 // ImageLoaderFactory factory = loaderFactoriesByExtension.get(extension); 395 // InputStream stream = ImageTools.createInputStream(input); 396 // if (stream != null) { 397 // loader = factory.createImageLoader(stream); 398 // if (listener != null) { 399 // loader.addListener(listener); 400 // } 401 // } 402 // } 403 // } 404 // 405 // return loader; 406 // } 407 408 private static ImageLoader getLoaderBySignature(InputStream stream, ImageLoadListener listener) throws IOException { 409 byte[] header = new byte[getMaxSignatureLength()]; 410 ImageTools.readFully(stream, header); 411 412 for (final Entry<Signature, ImageLoaderFactory> factoryRegistration: 413 loaderFactoriesBySignature.entrySet()) { 414 if (factoryRegistration.getKey().matches(header)) { 415 InputStream headerStream = new ByteArrayInputStream(header); 416 InputStream seqStream = new SequenceInputStream(headerStream, stream); 417 ImageLoader loader = factoryRegistration.getValue().createImageLoader(seqStream); 418 if (listener != null) { 419 loader.addListener(listener); 420 } 421 422 return loader; 423 } 424 } 425 426 // not found 427 return null; 428 } 429 430 private ImageStorage() { 431 } 432 }