/*
* Copyright (c) 2009, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.javafx.iio;
import com.sun.javafx.PlatformUtil;
import com.sun.javafx.iio.ImageFormatDescription.Signature;
import com.sun.javafx.iio.bmp.BMPImageLoaderFactory;
import com.sun.javafx.iio.common.ImageTools;
import com.sun.javafx.iio.gif.GIFImageLoaderFactory;
import com.sun.javafx.iio.ios.IosImageLoaderFactory;
import com.sun.javafx.iio.jpeg.JPEGImageLoaderFactory;
import com.sun.javafx.iio.png.PNGImageLoaderFactory;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
A convenience class for simple image loading. Factories for creating loaders
for image formats must be registered with this class.
/**
* A convenience class for simple image loading. Factories for creating loaders
* for image formats must be registered with this class.
*/
public class ImageStorage {
An enumeration of supported image types.
/**
* An enumeration of supported image types.
*/
public static enum ImageType {
An image with a single channel of 8-bit valued gray levels.
/**
* An image with a single channel of 8-bit valued gray levels.
*/
GRAY,
An image with with two 8-bit valued channels, one of gray levels,
the other of non-premultiplied opacity, ordered as GAGAGA...
/**
* An image with with two 8-bit valued channels, one of gray levels,
* the other of non-premultiplied opacity, ordered as GAGAGA...
*/
GRAY_ALPHA,
An image with with two 8-bit valued channels, one of gray levels,
the other of premultiplied opacity, ordered as GAGAGA...
/**
* An image with with two 8-bit valued channels, one of gray levels,
* the other of premultiplied opacity, ordered as GAGAGA...
*/
GRAY_ALPHA_PRE,
An image with with one 8-bit channel of indexes into a 24-bit
lookup table which maps the indexes to 8-bit RGB components.
/**
* An image with with one 8-bit channel of indexes into a 24-bit
* lookup table which maps the indexes to 8-bit RGB components.
*/
PALETTE,
An image with with one 8-bit channel of indexes into a 32-bit
lookup table which maps the indexes to 8-bit RGBA components
wherein the opacity is not-premultiplied.
/**
* An image with with one 8-bit channel of indexes into a 32-bit
* lookup table which maps the indexes to 8-bit RGBA components
* wherein the opacity is not-premultiplied.
*/
PALETTE_ALPHA,
An image with with one 8-bit channel of indexes into a 32-bit
lookup table which maps the indexes to 8-bit RGBA components
wherein the opacity is premultiplied.
/**
* An image with with one 8-bit channel of indexes into a 32-bit
* lookup table which maps the indexes to 8-bit RGBA components
* wherein the opacity is premultiplied.
*/
PALETTE_ALPHA_PRE,
An image with with one 8-bit channel of indexes into a 24-bit
lookup table which maps the indexes to 8-bit RGB components, and
a single transparent index to indicate the location of transparent
pixels.
/**
* An image with with one 8-bit channel of indexes into a 24-bit
* lookup table which maps the indexes to 8-bit RGB components, and
* a single transparent index to indicate the location of transparent
* pixels.
*/
PALETTE_TRANS,
An image with with three 8-bit valued channels of red, green, and
blue, respectively, ordered as RGBRGBRGB...
/**
* An image with with three 8-bit valued channels of red, green, and
* blue, respectively, ordered as RGBRGBRGB...
*/
RGB,
An image with with four 8-bit valued channels of red, green, blue,
and non-premultiplied opacity, respectively, ordered as
RGBARGBARGBA...
/**
* An image with with four 8-bit valued channels of red, green, blue,
* and non-premultiplied opacity, respectively, ordered as
* RGBARGBARGBA...
*/
RGBA,
An image with with four 8-bit valued channels of red, green, blue,
and premultiplied opacity, respectively, ordered as
RGBARGBARGBA...
/**
* An image with with four 8-bit valued channels of red, green, blue,
* and premultiplied opacity, respectively, ordered as
* RGBARGBARGBA...
*/
RGBA_PRE
};
/**
* A mapping of lower case file extensions to loader factories.
*/
// private static HashMap<String, ImageLoaderFactory> loaderFactoriesByExtension;
A mapping of format signature byte sequences to loader factories.
/**
* A mapping of format signature byte sequences to loader factories.
*/
private static final HashMap<Signature, ImageLoaderFactory> loaderFactoriesBySignature;
private static final ImageLoaderFactory[] loaderFactories;
private static final boolean isIOS = PlatformUtil.isIOS();
private static int maxSignatureLength;
static {
if (isIOS) {
//On iOS we have single factory/ native loader
//for all image formats
loaderFactories = new ImageLoaderFactory[]{
IosImageLoaderFactory.getInstance()
};
} else {
loaderFactories = new ImageLoaderFactory[]{
GIFImageLoaderFactory.getInstance(),
JPEGImageLoaderFactory.getInstance(),
PNGImageLoaderFactory.getInstance(),
BMPImageLoaderFactory.getInstance()
// Note: append ImageLoadFactory for any new format here.
};
}
// loaderFactoriesByExtension = new HashMap(numExtensions);
loaderFactoriesBySignature = new HashMap<Signature, ImageLoaderFactory>(loaderFactories.length);
for (int i = 0; i < loaderFactories.length; i++) {
addImageLoaderFactory(loaderFactories[i]);
}
}
public static ImageFormatDescription[] getSupportedDescriptions() {
ImageFormatDescription[] formats = new ImageFormatDescription[loaderFactories.length];
for (int i = 0; i < loaderFactories.length; i++) {
formats[i] = loaderFactories[i].getFormatDescription();
}
return (formats);
}
Returns the number of bands for a raw image of the specified type.
Params: - type – the type of image
Returns: the number of bands of a raw image of this type
/**
* Returns the number of bands for a raw image of the specified type.
*
* @param type the type of image
* @return the number of bands of a raw image of this type
*/
public static int getNumBands(ImageType type) {
int numBands = -1;
switch (type) {
case GRAY:
case PALETTE:
case PALETTE_ALPHA:
case PALETTE_ALPHA_PRE:
case PALETTE_TRANS:
numBands = 1;
break;
case GRAY_ALPHA:
case GRAY_ALPHA_PRE:
numBands = 2;
break;
case RGB:
numBands = 3;
break;
case RGBA:
case RGBA_PRE:
numBands = 4;
break;
default:
throw new IllegalArgumentException("Unknown ImageType " + type);
}
return numBands;
}
Registers an image loader factory. The factory replaces any other factory
previously registered for the file extensions (converted to lower case)
and signature indicated by the format description.
Params: - factory – the factory to register.
/**
* Registers an image loader factory. The factory replaces any other factory
* previously registered for the file extensions (converted to lower case)
* and signature indicated by the format description.
*
* @param factory the factory to register.
*/
public static void addImageLoaderFactory(ImageLoaderFactory factory) {
ImageFormatDescription desc = factory.getFormatDescription();
// String[] extensions = desc.getExtensions();
// for (int j = 0; j < extensions.length; j++) {
// loaderFactoriesByExtension.put(extensions[j].toLowerCase(), factory);
// }
for (final Signature signature: desc.getSignatures()) {
loaderFactoriesBySignature.put(signature, factory);
}
// invalidate max signature length
synchronized (ImageStorage.class) {
maxSignatureLength = -1;
}
}
Load all images present in the specified stream. The image will be
rescaled according to this algorithm:
int finalWidth, finalHeight; // final dimensions
int width, height; // specified maximum dimensions
// Use source dimensions as default values.
if (width <= 0) {
width = sourceWidth;
}
if (height <= 0) {
height = sourceHeight;
}
// If not downscaling reset the dimensions to those of the source.
if (!((width < sourceWidth && height <= sourceHeight) ||
(width <= sourceWidth && height < sourceHeight))) {
finalWidth = sourceWidth;
finalHeight = sourceHeight;
} else if(preserveAspectRatio) {
double r = (double) sourceWidth / (double) sourceHeight;
finalHeight = (int) ((width / r < height ? width / r : height) + 0.5);
finalWidth = (int) (r * finalHeight + 0.5);
} else {
finalWidth = width;
finalHeight = height;
}
Params: - input – the image data stream.
- listener – a listener to receive notifications about image loading.
- width – the desired width of the image; if non-positive,
the original image width will be used.
- height – the desired height of the image; if non-positive, the
original image height will be used.
- preserveAspectRatio – whether to preserve the width-to-height ratio
of the image.
- smooth – whether to apply smoothing when downsampling.
Returns: the sequence of all images in the specified source or
null
on error.
/**
* Load all images present in the specified stream. The image will be
* rescaled according to this algorithm:
*
* <code><pre>
* int finalWidth, finalHeight; // final dimensions
* int width, height; // specified maximum dimensions
* // Use source dimensions as default values.
* if (width <= 0) {
* width = sourceWidth;
* }
* if (height <= 0) {
* height = sourceHeight;
* }
* // If not downscaling reset the dimensions to those of the source.
* if (!((width < sourceWidth && height <= sourceHeight) ||
* (width <= sourceWidth && height < sourceHeight))) {
* finalWidth = sourceWidth;
* finalHeight = sourceHeight;
* } else if(preserveAspectRatio) {
* double r = (double) sourceWidth / (double) sourceHeight;
* finalHeight = (int) ((width / r < height ? width / r : height) + 0.5);
* finalWidth = (int) (r * finalHeight + 0.5);
* } else {
* finalWidth = width;
* finalHeight = height;
* }
* </pre></code>
*
* @param input the image data stream.
* @param listener a listener to receive notifications about image loading.
* @param width the desired width of the image; if non-positive,
* the original image width will be used.
* @param height the desired height of the image; if non-positive, the
* original image height will be used.
* @param preserveAspectRatio whether to preserve the width-to-height ratio
* of the image.
* @param smooth whether to apply smoothing when downsampling.
* @return the sequence of all images in the specified source or
* <code>null</code> on error.
*/
public static ImageFrame[] loadAll(InputStream input, ImageLoadListener listener,
double width, double height, boolean preserveAspectRatio,
float pixelScale, boolean smooth) throws ImageStorageException {
ImageLoader loader = null;
try {
if (isIOS) {
// no extension/signature recognition done here,
// we always want the iOS native loader
loader = IosImageLoaderFactory.getInstance().createImageLoader(input);
} else {
loader = getLoaderBySignature(input, listener);
}
} catch (IOException e) {
throw new ImageStorageException(e.getMessage(), e);
}
ImageFrame[] images = null;
if (loader != null) {
images = loadAll(loader, width, height, preserveAspectRatio, pixelScale, smooth);
} else {
throw new ImageStorageException("No loader for image data");
}
return images;
}
Load all images present in the specified input. For more details refer to loadAll(InputStream, ImageLoadListener, double, double, boolean, float)
. /**
* Load all images present in the specified input. For more details refer to
* {@link #loadAll(java.io.InputStream, com.sun.javafx.iio.ImageLoadListener, int, int, boolean, boolean)}.
*/
public static ImageFrame[] loadAll(String input, ImageLoadListener listener,
double width, double height, boolean preserveAspectRatio,
float devPixelScale, boolean smooth) throws ImageStorageException {
if (input == null || input.isEmpty()) {
throw new ImageStorageException("URL can't be null or empty");
}
ImageFrame[] images = null;
InputStream theStream = null;
ImageLoader loader = null;
try {
float imgPixelScale = 1.0f;
try {
if (devPixelScale >= 1.5f) {
// Use Mac Retina conventions for >= 1.5f
try {
String name2x = ImageTools.getScaledImageName(input);
theStream = ImageTools.createInputStream(name2x);
imgPixelScale = 2.0f;
} catch (IOException e) {
}
}
if (theStream == null) {
theStream = ImageTools.createInputStream(input);
}
if (isIOS) {
loader = IosImageLoaderFactory.getInstance().createImageLoader(theStream);
} else {
loader = getLoaderBySignature(theStream, listener);
}
} catch (IOException e) {
throw new ImageStorageException(e.getMessage(), e);
}
if (loader != null) {
images = loadAll(loader, width, height, preserveAspectRatio, imgPixelScale, smooth);
} else {
throw new ImageStorageException("No loader for image data");
}
} finally {
try {
if (theStream != null) {
theStream.close();
}
} catch (IOException e) {
}
}
return images;
}
private static synchronized int getMaxSignatureLength() {
if (maxSignatureLength < 0) {
maxSignatureLength = 0;
for (final Signature signature:
loaderFactoriesBySignature.keySet()) {
final int signatureLength = signature.getLength();
if (maxSignatureLength < signatureLength) {
maxSignatureLength = signatureLength;
}
}
}
return maxSignatureLength;
}
private static ImageFrame[] loadAll(ImageLoader loader,
double width, double height, boolean preserveAspectRatio,
float pixelScale, boolean smooth) throws ImageStorageException {
ImageFrame[] images = null;
ArrayList<ImageFrame> list = new ArrayList<ImageFrame>();
int imageIndex = 0;
ImageFrame image = null;
int imgw = (int) Math.round(width * pixelScale);
int imgh = (int) Math.round(height * pixelScale);
do {
try {
image = loader.load(imageIndex++, imgw, imgh, preserveAspectRatio, smooth);
} catch (Exception e) {
// allow partially loaded animated images
if (imageIndex > 1) {
break;
} else {
throw new ImageStorageException(e.getMessage(), e);
}
}
if (image != null) {
image.setPixelScale(pixelScale);
list.add(image);
} else {
break;
}
} while (true);
int numImages = list.size();
if (numImages > 0) {
images = new ImageFrame[numImages];
list.toArray(images);
}
return images;
}
// private static ImageLoader getLoaderByExtension(String input, ImageLoadListener listener) {
// ImageLoader loader = null;
//
// int dotIndex = input.lastIndexOf(".");
// if (dotIndex != -1) {
// String extension = input.substring(dotIndex + 1).toLowerCase();
// Set extensions = loaderFactoriesByExtension.keySet();
// if (extensions.contains(extension)) {
// ImageLoaderFactory factory = loaderFactoriesByExtension.get(extension);
// InputStream stream = ImageTools.createInputStream(input);
// if (stream != null) {
// loader = factory.createImageLoader(stream);
// if (listener != null) {
// loader.addListener(listener);
// }
// }
// }
// }
//
// return loader;
// }
private static ImageLoader getLoaderBySignature(InputStream stream, ImageLoadListener listener) throws IOException {
byte[] header = new byte[getMaxSignatureLength()];
ImageTools.readFully(stream, header);
for (final Entry<Signature, ImageLoaderFactory> factoryRegistration:
loaderFactoriesBySignature.entrySet()) {
if (factoryRegistration.getKey().matches(header)) {
InputStream headerStream = new ByteArrayInputStream(header);
InputStream seqStream = new SequenceInputStream(headerStream, stream);
ImageLoader loader = factoryRegistration.getValue().createImageLoader(seqStream);
if (listener != null) {
loader.addListener(listener);
}
return loader;
}
}
// not found
return null;
}
private ImageStorage() {
}
}