org.imajine.image.jai.JAIUtils Maven / Gradle / Ivy
/***********************************************************************************************************************
*
* Mistral - open source imaging engine
* Copyright (C) 2003-2012 by Tidalwave s.a.s.
*
***********************************************************************************************************************
*
* 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.
*
***********************************************************************************************************************
*
* WWW: http://mistral.imajine.org
* SCM: https://bitbucket.org/tidalwave/mistral-src
*
**********************************************************************************************************************/
package org.imajine.image.jai;
import java.awt.RenderingHints;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.Kernel;
import java.awt.image.WritableRaster;
import javax.media.jai.BorderExtender;
import javax.media.jai.Interpolation;
import javax.media.jai.KernelJAI;
import javax.media.jai.LookupTableJAI;
import javax.media.jai.PlanarImage;
import javax.media.jai.TiledImage;
import javax.media.jai.operator.BorderDescriptor;
import javax.media.jai.operator.ColorConvertDescriptor;
import javax.media.jai.operator.ConvolveDescriptor;
import javax.media.jai.operator.LookupDescriptor;
import javax.media.jai.operator.ScaleDescriptor;
import org.imajine.image.EditableImage;
import org.imajine.image.Quality;
import org.imajine.image.java2d.Java2DUtils;
import java.awt.image.Raster;
import lombok.extern.slf4j.Slf4j;
/*******************************************************************************
*
* @author Fabrizio Giudici
* @author Emmanuele Sordini
* @version $Id: JAIUtils.java,v 20e39e8c9fac 2012/02/02 16:35:02 fabrizio $
*
******************************************************************************/
@Slf4j
public class JAIUtils extends Java2DUtils
{
private static final int[] POWER2_SIZES =
{
32, 64, 128, 256, 512, 1024, 2048, 4096, 8192
};
/*******************************************************************************
*
* Apply a JAI Scale operation.
*
* @param scale the scale
* @param quality the quality
* @param hints the RenderingHints
*
******************************************************************************/
public static PlanarImage jaiMagnification (final PlanarImage source,
final double scale,
final Quality quality,
final RenderingHints hints)
{
assert scale >= 1;
PlanarImage result = source;
if (scale != 1)
{
log.debug(">>>> jaiMagnification({}, {})", scale, quality);
final Float fScale = new Float(scale);
final Float fZero = new Float(0);
Interpolation interpolation = null;
switch (quality)
{
case BEST:
interpolation = Interpolation.getInstance(Interpolation.INTERP_BICUBIC_2);
break;
case FASTEST:
interpolation = Interpolation.getInstance(Interpolation.INTERP_NEAREST);
break;
default:
throw new IllegalArgumentException(quality.toString());
}
log.debug(">>>> Scale({}, {}", scale, interpolation);
result = ScaleDescriptor.create(source, fScale, fScale, fZero, fZero, interpolation, hints);
logImage(log, ">>>>>>>> planarImage", result);
}
return result;
}
/*******************************************************************************
*
* Reduces the size of the given PlanarImage. This method applies distinct
* strategies in function of the scale and the quality. They are described in
* http://archives.java.sun.com/cgi-bin/wa?A2=ind0311&L=jai-interest&P=R9579&I=-3
*
* If quality == Quality.FASTEST, a JAI Scale operation is performed with
* nearest neighbor interpolation. This gives poor results but it's fast.
*
* If quality == Quality.BEST, we choose in function of the scale.
* If scale is in the range (0.5, 1), only a JAI Scale operation is applied with
* bilinear interpolation.
* If scale is in the range (0, 0.5], a low pass filter is first applied. Then
* a Scale operation is performed with nearest neighbor interpolation.
*
* This is probably what SubsampleAverage would do in a single pass, but it
* does not work in JAI 1.1.2_01 (will throw an ArrayIndexOutOfBoundsException
* when operation are committed such as in getAsBufferedImage()) - bug #4857245.
* FIXME: convert to SubsampleAverage when the bug will be fixed.
*
* In any case, if scale == 1, no operation is performed.
*
* @param source the source image
* @param scale the scale (must be < 1)
* @param quality the quality of the operation (Quality.FASTEST or Quality.BEST)
* @param hints the RenderingHints
* @return the processed image
*
******************************************************************************/
public static PlanarImage jaiReduction (final PlanarImage source,
final double scale,
final Quality quality,
final RenderingHints hints)
{
assert scale <= 1;
PlanarImage result = source;
if (scale != 1)
{
Float fScale = new Float(scale);
log.debug(">>>> jaiReduction({})", scale);
Integer iPad = new Integer(10); // FIXME
BorderExtender borderExtender = BorderExtender.createInstance(BorderExtender.BORDER_COPY);
log.debug(">>>>>>>> Border({})", iPad);
result = BorderDescriptor.create(result, iPad, iPad, iPad, iPad, borderExtender, hints);
switch (quality)
{
case FASTEST:
Interpolation interpolation = Interpolation.getInstance(Interpolation.INTERP_NEAREST);
log.debug(">>>>>>>> Scale({}, {})", scale, interpolation);
result = ScaleDescriptor.create(result, fScale, fScale, ZERO, ZERO, interpolation, hints);
break;
case BEST:
if (scale > 0.5)
{
interpolation = Interpolation.getInstance(Interpolation.INTERP_BILINEAR);
log.debug(">>>>>>>> Scale({}, {})", scale, interpolation);
result = ScaleDescriptor.create(result, fScale, fScale, ZERO, ZERO, interpolation, hints);
}
else // scale <= 0.5
{
final Kernel averagingKernel = getAveragingKernel((int)Math.round(1.0 / scale));
log.debug(">>>>>>>> Convolve() with averaging kernel: {}", averagingKernel);
result = ConvolveDescriptor.create(result, new KernelJAI(averagingKernel), hints);
interpolation = Interpolation.getInstance(Interpolation.INTERP_NEAREST);
log.debug(">>>>>>>> Scale({}, {})", scale, interpolation);
result = ScaleDescriptor.create(result, fScale, fScale, ZERO, ZERO, interpolation, hints);
}
break;
default:
throw new IllegalArgumentException(quality.toString());
}
// log.finer(">>>> jaiSubsampleAverage(" + scale + ")");
// Double dScale = new Double(scale);
// planarImage = SubsampleAverageDescriptor.create(planarImage, dScale, dScale, hints);
logImage(log, ">>>>>>>> jaiReduction returning planarImage", result);
}
return result;
}
/*******************************************************************************
*
* The copy is performed by copying rasters. This obviously presumes that the
* colorspaces are the same, otherwise a required conversion would be missing.
*
* BufferedImages returned by PlanarImage.getAsBufferedImage() are not processable
* by an AffineTransform, that could be use in future on the result. So we are
* creating an optimized BufferedImage with the same size and then we will copy
* the raster on it. See for instance
* http://www.javadesktop.org/forums/thread.jspa?messageID=8941
*
* @param destination the target BufferedImage
*
******************************************************************************/
public static void jaiCopyToBufferedImage (final PlanarImage source,
final BufferedImage destination)
{
log.debug(">>>> jaiCopyToBufferedImage()");
long now = System.currentTimeMillis();
int strategy = 1;
if (strategy == 1)
{
BufferedImage bufferedImage2 = source.getAsBufferedImage();
destination.getRaster().setRect(bufferedImage2.getRaster());
}
// FIXME: maybe this is faster, since it surely doesn't change any pixel, but we have more than a single tile here
else if (strategy == 2)
{
destination.getRaster().setRect(getRaster(source));
}
log.debug(">>>> bufferedImage: {}", destination.getColorModel());
log.debug(">>>> jaiCopyToBufferedImage() done in {} msec", (System.currentTimeMillis() - now));
}
/*******************************************************************************
*
* If the current image is not sRGB, apply a ColorConvert operation to convert
* it to sRGB.
*
* @param colorModel the target color model (must be a sRGB color space)
* @param hints the RenderingHints
*
******************************************************************************/
public static PlanarImage jaiConvertTosRGBColorProfile (final PlanarImage source,
final ColorModel colorModel,
final RenderingHints hints)
{
boolean is_sRGB = source.getColorModel().getColorSpace().isCS_sRGB();
log.debug(">>>> planarImage.is_sRGB: {}", is_sRGB);
if (!is_sRGB)
{
// Convert color as last, since if the image has been scaled there are less pixels to convert
log.debug(">>>> Applying ColorConvertDescriptor");
PlanarImage result = ColorConvertDescriptor.create(source, colorModel, hints);
logImage(log, ">>>>>>>> planarImage", result);
return result;
}
else
{
return source;
}
}
/*******************************************************************************
*
* Converts the current image to a 8 bit representation by applying a Lookup
* operation. It's supposed that hints contains a proper target ImageLayout.
*
* The best way is to go with a Lookup operation since it's faster and allows you
* to specify the new target ImageLayout in a single step.
*
* @param hints the RenderingHints
*
******************************************************************************/
public static PlanarImage jaiConvertTo8Bits (final PlanarImage source, final RenderingHints hints)
{
final int TARGET_BIT_COUNT = 8;
int bitsPerPixel = source.getSampleModel().getSampleSize(0);
log.debug(">>>> planarImage.bitsPerPixel: {}", bitsPerPixel);
if (bitsPerPixel > TARGET_BIT_COUNT)
{
byte[] tableData = new byte[1 << bitsPerPixel];
for (int i = 0; i < tableData.length; i++)
{
tableData[i] = (byte)(i >> (bitsPerPixel - 8));
}
log.debug(">>>> Applying LookupDescriptor");
LookupTableJAI lut = new LookupTableJAI(tableData);
PlanarImage result = LookupDescriptor.create(source, lut, hints);
logImage(log, ">>>>>>>> planarImage", result);
return result;
}
else
{
return source;
}
}
/*******************************************************************************
*
*
******************************************************************************/
public static WritableRaster getRaster (final PlanarImage source)
{
assert source.getNumXTiles() == 1 : "getNumXTiles is " + source.getNumXTiles();
assert source.getNumYTiles() == 1 : "getNumYTiles is " + source.getNumYTiles();
TiledImage tiledImage = new TiledImage(source, true);
return tiledImage.getWritableTile(0, 0);
// FIXME: in realta' per le TiledImage non esiste: ci sono tanti raster, uno
// per ogni tile, e quindi richiederne uno solo implica un bel po' di lavoro
// Verificare invece se si puo' eliminare questo metodo
// Si potrebbe eliminare se il demosaicing venisse implementato come un'operazione JAI.
}
/*******************************************************************************
*
*
******************************************************************************/
public static ColorModel getColorModel (PlanarImage planarImage, ICC_Profile colorProfile)
{
ColorSpace colorSpace = new ICC_ColorSpace(colorProfile);
return new ComponentColorModel(colorSpace, false, false, ColorModel.OPAQUE, planarImage.getSampleModel().getDataType());
}
/*******************************************************************************
*
*
******************************************************************************/
public static PlanarImage getPlanarImage (EditableImage editableImage)
{
return (PlanarImage)editableImage.getImageModel().getImage();
}
/*******************************************************************************
* Calculates the average pixel value of a given image. Only band 0 of the
* image is used, and the result is always of double
type
* regardless of image source pixel base type
*
* @param source source image
* @return average pixel value
******************************************************************************/
public static double jaiAverageValue(PlanarImage source)
{
Raster raster = source.getData();
double result = 0.0;
for (int x=0; x= size)
{
foundFlag = true;
index = i;
}
}
if (foundFlag)
{
result = POWER2_SIZES[index];
}
return result;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy