mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-04 12:05:29 -04:00
RIP MagickAccelerator & friends, remove JMagick dependency
This commit is contained in:
parent
3dfc0850cc
commit
5e5d530498
@ -28,13 +28,6 @@
|
|||||||
<artifactId>common-io</artifactId>
|
<artifactId>common-io</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>jmagick</groupId>
|
|
||||||
<artifactId>jmagick</artifactId>
|
|
||||||
<version>6.6.9</version>
|
|
||||||
<optional>true</optional>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
@ -1,55 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2008, Harald Kuhr
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* * Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* * Neither the name of the copyright holder nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package com.twelvemonkeys.image;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Magick
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
|
||||||
* @author last modified by $Author: haku $
|
|
||||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/Magick.java#1 $
|
|
||||||
*/
|
|
||||||
final class Magick {
|
|
||||||
static final boolean DEBUG = useDebug();
|
|
||||||
|
|
||||||
private static boolean useDebug() {
|
|
||||||
try {
|
|
||||||
return "TRUE".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.image.magick.debug"));
|
|
||||||
}
|
|
||||||
catch (Throwable t) {
|
|
||||||
// Most probably in case of a SecurityManager
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Magick() {}
|
|
||||||
}
|
|
@ -1,187 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2008, Harald Kuhr
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* * Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* * Neither the name of the copyright holder nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
package com.twelvemonkeys.image;
|
|
||||||
|
|
||||||
import com.twelvemonkeys.lang.SystemUtil;
|
|
||||||
import magick.MagickImage;
|
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.awt.image.BufferedImageOp;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class accelerates certain graphics operations, using
|
|
||||||
* JMagick and ImageMagick, if available.
|
|
||||||
* If those libraries are not installed, this class silently does nothing.
|
|
||||||
* <p>
|
|
||||||
* Set the system property {@code "com.twelvemonkeys.image.accel"} to
|
|
||||||
* {@code false}, to disable, even if JMagick is installed.
|
|
||||||
* Set the system property {@code "com.twelvemonkeys.image.magick.debug"} to
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
|
||||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/MagickAccelerator.java#3 $
|
|
||||||
*/
|
|
||||||
final class MagickAccelerator {
|
|
||||||
|
|
||||||
private static final boolean DEBUG = Magick.DEBUG;
|
|
||||||
private static final boolean USE_MAGICK = useMagick();
|
|
||||||
|
|
||||||
private static final int RESAMPLE_OP = 0;
|
|
||||||
|
|
||||||
private static Class[] nativeOp = new Class[1];
|
|
||||||
|
|
||||||
static {
|
|
||||||
try {
|
|
||||||
nativeOp[RESAMPLE_OP] = Class.forName("com.twelvemonkeys.image.ResampleOp");
|
|
||||||
}
|
|
||||||
catch (ClassNotFoundException e) {
|
|
||||||
System.err.println("Could not find class: " + e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean useMagick() {
|
|
||||||
try {
|
|
||||||
boolean available = SystemUtil.isClassAvailable("magick.MagickImage");
|
|
||||||
|
|
||||||
if (DEBUG && !available) {
|
|
||||||
System.err.print("ImageMagick bindings not available.");
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean useMagick =
|
|
||||||
available && !"FALSE".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.image.accel"));
|
|
||||||
|
|
||||||
if (DEBUG) {
|
|
||||||
System.err.println(
|
|
||||||
useMagick
|
|
||||||
? "Will use ImageMagick bindings to accelerate image resampling operations."
|
|
||||||
: "Will not use ImageMagick to accelerate image resampling operations."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return useMagick;
|
|
||||||
}
|
|
||||||
catch (Throwable t) {
|
|
||||||
// Most probably in case of a SecurityManager
|
|
||||||
System.err.println("Could not enable ImageMagick bindings: " + t);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int getNativeOpIndex(Class pOpClass) {
|
|
||||||
for (int i = 0; i < nativeOp.length; i++) {
|
|
||||||
if (pOpClass == nativeOp[i]) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BufferedImage filter(BufferedImageOp pOperation, BufferedImage pInput, BufferedImage pOutput) {
|
|
||||||
if (!USE_MAGICK) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
BufferedImage result = null;
|
|
||||||
switch (getNativeOpIndex(pOperation.getClass())) {
|
|
||||||
case RESAMPLE_OP:
|
|
||||||
ResampleOp resample = (ResampleOp) pOperation;
|
|
||||||
result = resampleMagick(pInput, resample.width, resample.height, resample.filterType);
|
|
||||||
|
|
||||||
// NOTE: If output parameter is non-null, we have to return that
|
|
||||||
// image, instead of result
|
|
||||||
if (pOutput != null) {
|
|
||||||
//pOutput.setData(result.getRaster()); // Fast, but less compatible
|
|
||||||
// NOTE: For some reason, this is sometimes super-slow...?
|
|
||||||
ImageUtil.drawOnto(pOutput, result);
|
|
||||||
result = pOutput;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Simply fall through, allowing acceleration to be added later
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BufferedImage resampleMagick(BufferedImage pSrc, int pWidth, int pHeight, int pFilterType) {
|
|
||||||
// Convert to Magick, scale and convert back
|
|
||||||
MagickImage image = null;
|
|
||||||
MagickImage scaled = null;
|
|
||||||
try {
|
|
||||||
image = MagickUtil.toMagick(pSrc);
|
|
||||||
|
|
||||||
long start = 0;
|
|
||||||
if (DEBUG) {
|
|
||||||
start = System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: setFilter affects zoomImage, NOT scaleImage
|
|
||||||
image.setFilter(pFilterType);
|
|
||||||
scaled = image.zoomImage(pWidth, pHeight);
|
|
||||||
//scaled = image.scaleImage(pWidth, pHeight); // AREA_AVERAGING
|
|
||||||
|
|
||||||
if (DEBUG) {
|
|
||||||
long time = System.currentTimeMillis() - start;
|
|
||||||
System.out.println("Filtered: " + time + " ms");
|
|
||||||
}
|
|
||||||
|
|
||||||
return MagickUtil.toBuffered(scaled);
|
|
||||||
}
|
|
||||||
//catch (MagickException e) {
|
|
||||||
catch (Exception e) {
|
|
||||||
// NOTE: Stupid workaround: If MagickException is caught, a
|
|
||||||
// NoClassDefFoundError is thrown, when MagickException class is
|
|
||||||
// unavailable...
|
|
||||||
if (e instanceof RuntimeException) {
|
|
||||||
throw (RuntimeException) e;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ImageConversionException(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
// NOTE: ImageMagick might be unstable after a while, if image data
|
|
||||||
// is not deallocated. The GC/finalize method handles this, but in
|
|
||||||
// special circumstances, it's not triggered often enough.
|
|
||||||
if (image != null) {
|
|
||||||
image.destroyImages();
|
|
||||||
}
|
|
||||||
if (scaled != null) {
|
|
||||||
scaled.destroyImages();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,621 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2008, Harald Kuhr
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* * Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* * Neither the name of the copyright holder nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.twelvemonkeys.image;
|
|
||||||
|
|
||||||
import magick.ImageType;
|
|
||||||
import magick.MagickException;
|
|
||||||
import magick.MagickImage;
|
|
||||||
import magick.PixelPacket;
|
|
||||||
|
|
||||||
import java.awt.*;
|
|
||||||
import java.awt.color.ColorSpace;
|
|
||||||
import java.awt.color.ICC_ColorSpace;
|
|
||||||
import java.awt.color.ICC_Profile;
|
|
||||||
import java.awt.image.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility for converting JMagick {@code MagickImage}s to standard Java
|
|
||||||
* {@code BufferedImage}s and back.
|
|
||||||
* <p>
|
|
||||||
* <em>NOTE: This class is considered an implementation detail and not part of
|
|
||||||
* the public API. This class is subject to change without further notice.
|
|
||||||
* You have been warned. :-)</em>
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
|
||||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/MagickUtil.java#4 $
|
|
||||||
*/
|
|
||||||
public final class MagickUtil {
|
|
||||||
// IMPORTANT NOTE: Disaster happens if any of these constants are used outside this class
|
|
||||||
// because you then have a dependency on MagickException (this is due to Java class loading
|
|
||||||
// and initialization magic).
|
|
||||||
// Do not use outside this class. If the constants need to be shared, move to Magick or ImageUtil.
|
|
||||||
|
|
||||||
/** Color Model usesd for bilevel (B/W) */
|
|
||||||
private static final IndexColorModel CM_MONOCHROME = MonochromeColorModel.getInstance();
|
|
||||||
|
|
||||||
/** Color Model usesd for raw ABGR */
|
|
||||||
private static final ColorModel CM_COLOR_ALPHA =
|
|
||||||
new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {8, 8, 8, 8},
|
|
||||||
true, true, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
|
|
||||||
|
|
||||||
/** Color Model usesd for raw BGR */
|
|
||||||
private static final ColorModel CM_COLOR_OPAQUE =
|
|
||||||
new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {8, 8, 8},
|
|
||||||
false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
|
|
||||||
|
|
||||||
/** Color Model usesd for raw RGB */
|
|
||||||
//private static final ColorModel CM_COLOR_RGB = new DirectColorModel(24, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x0);
|
|
||||||
|
|
||||||
/** Color Model usesd for raw GRAY + ALPHA */
|
|
||||||
private static final ColorModel CM_GRAY_ALPHA =
|
|
||||||
new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY),
|
|
||||||
true, true, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
|
|
||||||
|
|
||||||
/** Color Model usesd for raw GRAY */
|
|
||||||
private static final ColorModel CM_GRAY_OPAQUE =
|
|
||||||
new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY),
|
|
||||||
false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
|
|
||||||
|
|
||||||
/** Band offsets for raw ABGR */
|
|
||||||
private static final int[] BAND_OFF_TRANS = new int[] {3, 2, 1, 0};
|
|
||||||
|
|
||||||
/** Band offsets for raw BGR */
|
|
||||||
private static final int[] BAND_OFF_OPAQUE = new int[] {2, 1, 0};
|
|
||||||
|
|
||||||
/** The point at {@code 0, 0} */
|
|
||||||
private static final Point LOCATION_UPPER_LEFT = new Point(0, 0);
|
|
||||||
|
|
||||||
private static final boolean DEBUG = Magick.DEBUG;
|
|
||||||
|
|
||||||
// Only static members and methods
|
|
||||||
private MagickUtil() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a {@code MagickImage} to a {@code BufferedImage}.
|
|
||||||
* <p>
|
|
||||||
* The conversion depends on {@code pImage}'s {@code ImageType}:
|
|
||||||
* </p>
|
|
||||||
* <dl>
|
|
||||||
* <dt>{@code ImageType.BilevelType}</dt>
|
|
||||||
* <dd>{@code BufferedImage} of type {@code TYPE_BYTE_BINARY}</dd>
|
|
||||||
*
|
|
||||||
* <dt>{@code ImageType.GrayscaleType}</dt>
|
|
||||||
* <dd>{@code BufferedImage} of type {@code TYPE_BYTE_GRAY}</dd>
|
|
||||||
* <dt>{@code ImageType.GrayscaleMatteType}</dt>
|
|
||||||
* <dd>{@code BufferedImage} of type {@code TYPE_USHORT_GRAY}</dd>
|
|
||||||
*
|
|
||||||
* <dt>{@code ImageType.PaletteType}</dt>
|
|
||||||
* <dd>{@code BufferedImage} of type {@code TYPE_BYTE_BINARY} (for images
|
|
||||||
* with a palette of <= 16 colors) or {@code TYPE_BYTE_INDEXED}</dd>
|
|
||||||
* <dt>{@code ImageType.PaletteMatteType}</dt>
|
|
||||||
* <dd>{@code BufferedImage} of type {@code TYPE_BYTE_BINARY} (for images
|
|
||||||
* with a palette of <= 16 colors) or {@code TYPE_BYTE_INDEXED}</dd>
|
|
||||||
*
|
|
||||||
* <dt>{@code ImageType.TrueColorType}</dt>
|
|
||||||
* <dd>{@code BufferedImage} of type {@code TYPE_3BYTE_BGR}</dd>
|
|
||||||
* <dt>{@code ImageType.TrueColorPaletteType}</dt>
|
|
||||||
* <dd>{@code BufferedImage} of type {@code TYPE_4BYTE_ABGR}</dd>
|
|
||||||
* </dl>
|
|
||||||
*
|
|
||||||
* @param pImage the original {@code MagickImage}
|
|
||||||
* @return a new {@code BufferedImage}
|
|
||||||
*
|
|
||||||
* @throws IllegalArgumentException if {@code pImage} is {@code null}
|
|
||||||
* or if the {@code ImageType} is not one mentioned above.
|
|
||||||
* @throws MagickException if an exception occurs during conversion
|
|
||||||
*
|
|
||||||
* @see BufferedImage
|
|
||||||
*/
|
|
||||||
public static BufferedImage toBuffered(MagickImage pImage) throws MagickException {
|
|
||||||
if (pImage == null) {
|
|
||||||
throw new IllegalArgumentException("image == null");
|
|
||||||
}
|
|
||||||
|
|
||||||
long start = 0L;
|
|
||||||
if (DEBUG) {
|
|
||||||
start = System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
|
|
||||||
BufferedImage image = null;
|
|
||||||
try {
|
|
||||||
switch (pImage.getImageType()) {
|
|
||||||
case ImageType.BilevelType:
|
|
||||||
image = bilevelToBuffered(pImage);
|
|
||||||
break;
|
|
||||||
case ImageType.GrayscaleType:
|
|
||||||
image = grayToBuffered(pImage, false);
|
|
||||||
break;
|
|
||||||
case ImageType.GrayscaleMatteType:
|
|
||||||
image = grayToBuffered(pImage, true);
|
|
||||||
break;
|
|
||||||
case ImageType.PaletteType:
|
|
||||||
image = paletteToBuffered(pImage, false);
|
|
||||||
break;
|
|
||||||
case ImageType.PaletteMatteType:
|
|
||||||
image = paletteToBuffered(pImage, true);
|
|
||||||
break;
|
|
||||||
case ImageType.TrueColorType:
|
|
||||||
image = rgbToBuffered(pImage, false);
|
|
||||||
break;
|
|
||||||
case ImageType.TrueColorMatteType:
|
|
||||||
image = rgbToBuffered(pImage, true);
|
|
||||||
break;
|
|
||||||
case ImageType.ColorSeparationType:
|
|
||||||
image = cmykToBuffered(pImage, false);
|
|
||||||
break;
|
|
||||||
case ImageType.ColorSeparationMatteType:
|
|
||||||
image = cmykToBuffered(pImage, true);
|
|
||||||
break;
|
|
||||||
case ImageType.OptimizeType:
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unknown JMagick image type: " + pImage.getImageType());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
if (DEBUG) {
|
|
||||||
long time = System.currentTimeMillis() - start;
|
|
||||||
System.out.println("Converted JMagick image type: " + pImage.getImageType() + " to BufferedImage: " + image);
|
|
||||||
System.out.println("Conversion to BufferedImage: " + time + " ms");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a {@code BufferedImage} to a {@code MagickImage}.
|
|
||||||
* <p>
|
|
||||||
* The conversion depends on {@code pImage}'s {@code ColorModel}:
|
|
||||||
* </p>
|
|
||||||
* <dl>
|
|
||||||
* <dt>{@code IndexColorModel} with 1 bit b/w</dt>
|
|
||||||
* <dd>{@code MagickImage} of type {@code ImageType.BilevelType}</dd>
|
|
||||||
* <dt>{@code IndexColorModel} > 1 bit,</dt>
|
|
||||||
* <dd>{@code MagickImage} of type {@code ImageType.PaletteType}
|
|
||||||
* or {@code MagickImage} of type {@code ImageType.PaletteMatteType}
|
|
||||||
* depending on <tt>ColorModel.getAlpha()</tt></dd>
|
|
||||||
*
|
|
||||||
* <dt>{@code ColorModel.getColorSpace().getType() == ColorSpace.TYPE_GRAY}</dt>
|
|
||||||
* <dd>{@code MagickImage} of type {@code ImageType.GrayscaleType}
|
|
||||||
* or {@code MagickImage} of type {@code ImageType.GrayscaleMatteType}
|
|
||||||
* depending on <tt>ColorModel.getAlpha()</tt></dd>
|
|
||||||
*
|
|
||||||
* <dt>{@code ColorModel.getColorSpace().getType() == ColorSpace.TYPE_RGB}</dt>
|
|
||||||
* <dd>{@code MagickImage} of type {@code ImageType.TrueColorType}
|
|
||||||
* or {@code MagickImage} of type {@code ImageType.TrueColorPaletteType}</dd>
|
|
||||||
* </dl>
|
|
||||||
*
|
|
||||||
* @param pImage the original {@code BufferedImage}
|
|
||||||
* @return a new {@code MagickImage}
|
|
||||||
*
|
|
||||||
* @throws IllegalArgumentException if {@code pImage} is {@code null}
|
|
||||||
* or if the {@code ColorModel} is not one mentioned above.
|
|
||||||
* @throws MagickException if an exception occurs during conversion
|
|
||||||
*
|
|
||||||
* @see BufferedImage
|
|
||||||
*/
|
|
||||||
public static MagickImage toMagick(BufferedImage pImage) throws MagickException {
|
|
||||||
if (pImage == null) {
|
|
||||||
throw new IllegalArgumentException("image == null");
|
|
||||||
}
|
|
||||||
|
|
||||||
long start = 0L;
|
|
||||||
if (DEBUG) {
|
|
||||||
start = System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
ColorModel cm = pImage.getColorModel();
|
|
||||||
if (cm instanceof IndexColorModel) {
|
|
||||||
// Handles both BilevelType, PaletteType and PaletteMatteType
|
|
||||||
return indexedToMagick(pImage, (IndexColorModel) cm, cm.hasAlpha());
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (cm.getColorSpace().getType()) {
|
|
||||||
case ColorSpace.TYPE_GRAY:
|
|
||||||
// Handles GrayType and GrayMatteType
|
|
||||||
return grayToMagick(pImage, cm.hasAlpha());
|
|
||||||
case ColorSpace.TYPE_RGB:
|
|
||||||
// Handles TrueColorType and TrueColorMatteType
|
|
||||||
return rgbToMagic(pImage, cm.hasAlpha());
|
|
||||||
case ColorSpace.TYPE_CMY:
|
|
||||||
case ColorSpace.TYPE_CMYK:
|
|
||||||
case ColorSpace.TYPE_HLS:
|
|
||||||
case ColorSpace.TYPE_HSV:
|
|
||||||
// Other types not supported yet
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unknown buffered image type: " + pImage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
if (DEBUG) {
|
|
||||||
long time = System.currentTimeMillis() - start;
|
|
||||||
System.out.println("Conversion to MagickImage: " + time + " ms");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MagickImage rgbToMagic(BufferedImage pImage, boolean pAlpha) throws MagickException {
|
|
||||||
MagickImage image = new MagickImage();
|
|
||||||
|
|
||||||
BufferedImage buffered = ImageUtil.toBuffered(pImage, pAlpha ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR);
|
|
||||||
|
|
||||||
// Need to get data of sub raster, not the full data array, this is
|
|
||||||
// just a convenient way
|
|
||||||
Raster raster;
|
|
||||||
if (buffered.getRaster().getParent() != null) {
|
|
||||||
raster = buffered.getData(new Rectangle(buffered.getWidth(), buffered.getHeight()));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
raster = buffered.getRaster();
|
|
||||||
}
|
|
||||||
|
|
||||||
image.constituteImage(buffered.getWidth(), buffered.getHeight(), pAlpha ? "ABGR" : "BGR",
|
|
||||||
((DataBufferByte) raster.getDataBuffer()).getData());
|
|
||||||
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MagickImage grayToMagick(BufferedImage pImage, boolean pAlpha) throws MagickException {
|
|
||||||
MagickImage image = new MagickImage();
|
|
||||||
|
|
||||||
// TODO: Make a fix for TYPE_USHORT_GRAY
|
|
||||||
// The code below does not seem to work (JMagick issues?)...
|
|
||||||
/*
|
|
||||||
if (pImage.getType() == BufferedImage.TYPE_USHORT_GRAY) {
|
|
||||||
short[] data = ((DataBufferUShort) pImage.getRaster().getDataBuffer()).getData();
|
|
||||||
int[] intData = new int[data.length];
|
|
||||||
for (int i = 0; i < data.length; i++) {
|
|
||||||
intData[i] = (data[i] & 0xffff) * 0xffff;
|
|
||||||
}
|
|
||||||
image.constituteImage(pImage.getWidth(), pImage.getHeight(), "I", intData);
|
|
||||||
|
|
||||||
System.out.println("storageClass: " + image.getStorageClass());
|
|
||||||
System.out.println("depth: " + image.getDepth());
|
|
||||||
System.out.println("imageType: " + image.getImageType());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
*/
|
|
||||||
BufferedImage buffered = ImageUtil.toBuffered(pImage, pAlpha ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_BYTE_GRAY);
|
|
||||||
|
|
||||||
// Need to get data of sub raster, not the full data array, this is
|
|
||||||
// just a convenient way
|
|
||||||
Raster raster;
|
|
||||||
if (buffered.getRaster().getParent() != null) {
|
|
||||||
raster = buffered.getData(new Rectangle(buffered.getWidth(), buffered.getHeight()));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
raster = buffered.getRaster();
|
|
||||||
}
|
|
||||||
|
|
||||||
image.constituteImage(buffered.getWidth(), buffered.getHeight(), pAlpha ? "ABGR" : "I", ((DataBufferByte) raster.getDataBuffer()).getData());
|
|
||||||
//}
|
|
||||||
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MagickImage indexedToMagick(BufferedImage pImage, IndexColorModel pColorModel, boolean pAlpha) throws MagickException {
|
|
||||||
MagickImage image = rgbToMagic(pImage, pAlpha);
|
|
||||||
|
|
||||||
int mapSize = pColorModel.getMapSize();
|
|
||||||
image.setNumberColors(mapSize);
|
|
||||||
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
public static MagickImage toMagick(BufferedImage pImage) throws MagickException {
|
|
||||||
if (pImage == null) {
|
|
||||||
throw new IllegalArgumentException("image == null");
|
|
||||||
}
|
|
||||||
|
|
||||||
final int width = pImage.getWidth();
|
|
||||||
final int height = pImage.getHeight();
|
|
||||||
|
|
||||||
// int ARGB -> byte RGBA conversion
|
|
||||||
// NOTE: This is ImageMagick Q16 compatible raw RGBA format with 16 bits/sample...
|
|
||||||
// For a Q8 build, we could probably go with half the space...
|
|
||||||
// NOTE: This is close to insanity, as it wastes extreme ammounts of memory
|
|
||||||
final int[] argb = new int[width];
|
|
||||||
final byte[] raw16 = new byte[width * height * 8];
|
|
||||||
for (int y = 0; y < height; y++) {
|
|
||||||
// Fetch one line of ARGB data
|
|
||||||
pImage.getRGB(0, y, width, 1, argb, 0, width);
|
|
||||||
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
int pixel = (x + (y * width)) * 8;
|
|
||||||
raw16[pixel ] = (byte) ((argb[x] >> 16) & 0xff); // R
|
|
||||||
raw16[pixel + 2] = (byte) ((argb[x] >> 8) & 0xff); // G
|
|
||||||
raw16[pixel + 4] = (byte) ((argb[x] ) & 0xff); // B
|
|
||||||
raw16[pixel + 6] = (byte) ((argb[x] >> 24) & 0xff); // A
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create magick image
|
|
||||||
ImageInfo info = new ImageInfo();
|
|
||||||
info.setMagick("RGBA"); // Raw RGBA samples
|
|
||||||
info.setSize(width + "x" + height); // String?!?
|
|
||||||
|
|
||||||
MagickImage image = new MagickImage(info);
|
|
||||||
image.setImageAttribute("depth", "8");
|
|
||||||
|
|
||||||
// Set pixel data in 16 bit raw RGBA format
|
|
||||||
image.blobToImage(info, raw16);
|
|
||||||
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a bi-level {@code MagickImage} to a {@code BufferedImage}, of
|
|
||||||
* type {@code TYPE_BYTE_BINARY}.
|
|
||||||
*
|
|
||||||
* @param pImage the original {@code MagickImage}
|
|
||||||
* @return a new {@code BufferedImage}
|
|
||||||
*
|
|
||||||
* @throws MagickException if an exception occurs during conversion
|
|
||||||
*
|
|
||||||
* @see BufferedImage
|
|
||||||
*/
|
|
||||||
private static BufferedImage bilevelToBuffered(MagickImage pImage) throws MagickException {
|
|
||||||
// As there is no way to get the binary representation of the image,
|
|
||||||
// convert to gray, and the create a binary image from it
|
|
||||||
BufferedImage temp = grayToBuffered(pImage, false);
|
|
||||||
|
|
||||||
BufferedImage image = new BufferedImage(temp.getWidth(), temp.getHeight(), BufferedImage.TYPE_BYTE_BINARY, CM_MONOCHROME);
|
|
||||||
|
|
||||||
ImageUtil.drawOnto(image, temp);
|
|
||||||
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a gray {@code MagickImage} to a {@code BufferedImage}, of
|
|
||||||
* type {@code TYPE_USHORT_GRAY} or {@code TYPE_BYTE_GRAY}.
|
|
||||||
*
|
|
||||||
* @param pImage the original {@code MagickImage}
|
|
||||||
* @param pAlpha keep alpha channel
|
|
||||||
* @return a new {@code BufferedImage}
|
|
||||||
*
|
|
||||||
* @throws MagickException if an exception occurs during conversion
|
|
||||||
*
|
|
||||||
* @see BufferedImage
|
|
||||||
*/
|
|
||||||
private static BufferedImage grayToBuffered(MagickImage pImage, boolean pAlpha) throws MagickException {
|
|
||||||
Dimension size = pImage.getDimension();
|
|
||||||
int length = size.width * size.height;
|
|
||||||
int bands = pAlpha ? 2 : 1;
|
|
||||||
byte[] pixels = new byte[length * bands];
|
|
||||||
|
|
||||||
// TODO: Make a fix for 16 bit TYPE_USHORT_GRAY?!
|
|
||||||
// Note: The ordering AI or I corresponds to BufferedImage
|
|
||||||
// TYPE_CUSTOM and TYPE_BYTE_GRAY respectively
|
|
||||||
pImage.dispatchImage(0, 0, size.width, size.height, pAlpha ? "AI" : "I", pixels);
|
|
||||||
|
|
||||||
// Init databuffer with array, to avoid allocation of empty array
|
|
||||||
DataBuffer buffer = new DataBufferByte(pixels, pixels.length);
|
|
||||||
|
|
||||||
int[] bandOffsets = pAlpha ? new int[] {1, 0} : new int[] {0};
|
|
||||||
|
|
||||||
WritableRaster raster =
|
|
||||||
Raster.createInterleavedRaster(buffer, size.width, size.height,
|
|
||||||
size.width * bands, bands, bandOffsets, LOCATION_UPPER_LEFT);
|
|
||||||
|
|
||||||
return new BufferedImage(pAlpha ? CM_GRAY_ALPHA : CM_GRAY_OPAQUE, raster, pAlpha, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a palette-based {@code MagickImage} to a
|
|
||||||
* {@code BufferedImage}, of type {@code TYPE_BYTE_BINARY} (for images
|
|
||||||
* with a palette of <= 16 colors) or {@code TYPE_BYTE_INDEXED}.
|
|
||||||
*
|
|
||||||
* @param pImage the original {@code MagickImage}
|
|
||||||
* @param pAlpha keep alpha channel
|
|
||||||
* @return a new {@code BufferedImage}
|
|
||||||
*
|
|
||||||
* @throws MagickException if an exception occurs during conversion
|
|
||||||
*
|
|
||||||
* @see BufferedImage
|
|
||||||
*/
|
|
||||||
private static BufferedImage paletteToBuffered(MagickImage pImage, boolean pAlpha) throws MagickException {
|
|
||||||
// Create indexcolormodel for the image
|
|
||||||
IndexColorModel cm;
|
|
||||||
|
|
||||||
try {
|
|
||||||
cm = createIndexColorModel(pImage.getColormap(), pAlpha);
|
|
||||||
}
|
|
||||||
catch (MagickException e) {
|
|
||||||
// NOTE: Some MagickImages incorrecly (?) reports to be paletteType,
|
|
||||||
// but does not have a colormap, this is a workaround.
|
|
||||||
return rgbToBuffered(pImage, pAlpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
// As there is no way to get the indexes of an indexed image, convert to
|
|
||||||
// RGB, and the create an indexed image from it
|
|
||||||
BufferedImage temp = rgbToBuffered(pImage, pAlpha);
|
|
||||||
|
|
||||||
BufferedImage image;
|
|
||||||
if (cm.getMapSize() <= 16) {
|
|
||||||
image = new BufferedImage(temp.getWidth(), temp.getHeight(), BufferedImage.TYPE_BYTE_BINARY, cm);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
image = new BufferedImage(temp.getWidth(), temp.getHeight(), BufferedImage.TYPE_BYTE_INDEXED, cm);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create transparent background for images containing alpha
|
|
||||||
if (pAlpha) {
|
|
||||||
Graphics2D g = image.createGraphics();
|
|
||||||
try {
|
|
||||||
g.setComposite(AlphaComposite.Clear);
|
|
||||||
g.fillRect(0, 0, temp.getWidth(), temp.getHeight());
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
g.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: This is (surprisingly) much faster than using g2d.drawImage()..
|
|
||||||
// (Tests shows 20-30ms, vs. 600-700ms on the same image)
|
|
||||||
BufferedImageOp op = new CopyDither(cm);
|
|
||||||
op.filter(temp, image);
|
|
||||||
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an {@code IndexColorModel} from an array of
|
|
||||||
* {@code PixelPacket}s.
|
|
||||||
*
|
|
||||||
* @param pColormap the original colormap as a {@code PixelPacket} array
|
|
||||||
* @param pAlpha keep alpha channel
|
|
||||||
*
|
|
||||||
* @return a new {@code IndexColorModel}
|
|
||||||
*/
|
|
||||||
public static IndexColorModel createIndexColorModel(PixelPacket[] pColormap, boolean pAlpha) {
|
|
||||||
int[] colors = new int[pColormap.length];
|
|
||||||
|
|
||||||
// TODO: Verify if this is correct for alpha...?
|
|
||||||
int trans = pAlpha ? colors.length - 1 : -1;
|
|
||||||
|
|
||||||
//for (int i = 0; i < pColormap.length; i++) {
|
|
||||||
for (int i = pColormap.length - 1; i != 0; i--) {
|
|
||||||
PixelPacket color = pColormap[i];
|
|
||||||
if (pAlpha) {
|
|
||||||
colors[i] = (0xff - (color.getOpacity() & 0xff)) << 24 |
|
|
||||||
(color.getRed() & 0xff) << 16 |
|
|
||||||
(color.getGreen() & 0xff) << 8 |
|
|
||||||
(color.getBlue() & 0xff);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
colors[i] = (color.getRed() & 0xff) << 16 |
|
|
||||||
(color.getGreen() & 0xff) << 8 |
|
|
||||||
(color.getBlue() & 0xff);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new InverseColorMapIndexColorModel(8, colors.length, colors, 0, pAlpha, trans, DataBuffer.TYPE_BYTE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts an (A)RGB {@code MagickImage} to a {@code BufferedImage}, of
|
|
||||||
* type {@code TYPE_4BYTE_ABGR} or {@code TYPE_3BYTE_BGR}.
|
|
||||||
*
|
|
||||||
* @param pImage the original {@code MagickImage}
|
|
||||||
* @param pAlpha keep alpha channel
|
|
||||||
* @return a new {@code BufferedImage}
|
|
||||||
*
|
|
||||||
* @throws MagickException if an exception occurs during conversion
|
|
||||||
*
|
|
||||||
* @see BufferedImage
|
|
||||||
*/
|
|
||||||
private static BufferedImage rgbToBuffered(MagickImage pImage, boolean pAlpha) throws MagickException {
|
|
||||||
Dimension size = pImage.getDimension();
|
|
||||||
int length = size.width * size.height;
|
|
||||||
int bands = pAlpha ? 4 : 3;
|
|
||||||
byte[] pixels = new byte[length * bands];
|
|
||||||
|
|
||||||
// TODO: If we do multiple dispatches (one per line, typically), we could provide listener
|
|
||||||
// feedback. But it's currently a lot slower than fetching all the pixels in one go.
|
|
||||||
|
|
||||||
// Note: The ordering ABGR or BGR corresponds to BufferedImage
|
|
||||||
// TYPE_4BYTE_ABGR and TYPE_3BYTE_BGR respectively
|
|
||||||
pImage.dispatchImage(0, 0, size.width, size.height, pAlpha ? "ABGR" : "BGR", pixels);
|
|
||||||
|
|
||||||
// Init databuffer with array, to avoid allocation of empty array
|
|
||||||
DataBuffer buffer = new DataBufferByte(pixels, pixels.length);
|
|
||||||
|
|
||||||
int[] bandOffsets = pAlpha ? BAND_OFF_TRANS : BAND_OFF_OPAQUE;
|
|
||||||
|
|
||||||
WritableRaster raster =
|
|
||||||
Raster.createInterleavedRaster(buffer, size.width, size.height,
|
|
||||||
size.width * bands, bands, bandOffsets, LOCATION_UPPER_LEFT);
|
|
||||||
|
|
||||||
return new BufferedImage(pAlpha ? CM_COLOR_ALPHA : CM_COLOR_OPAQUE, raster, pAlpha, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts an {@code MagickImage} to a {@code BufferedImage} which holds an CMYK ICC profile
|
|
||||||
*
|
|
||||||
* @param pImage the original {@code MagickImage}
|
|
||||||
* @param pAlpha keep alpha channel
|
|
||||||
* @return a new {@code BufferedImage}
|
|
||||||
*
|
|
||||||
* @throws MagickException if an exception occurs during conversion
|
|
||||||
*
|
|
||||||
* @see BufferedImage
|
|
||||||
*/
|
|
||||||
private static BufferedImage cmykToBuffered(MagickImage pImage, boolean pAlpha) throws MagickException {
|
|
||||||
Dimension size = pImage.getDimension();
|
|
||||||
int length = size.width * size.height;
|
|
||||||
|
|
||||||
// Retreive the ICC profile
|
|
||||||
ICC_Profile profile = ICC_Profile.getInstance(pImage.getColorProfile().getInfo());
|
|
||||||
ColorSpace cs = new ICC_ColorSpace(profile);
|
|
||||||
|
|
||||||
int bands = cs.getNumComponents() + (pAlpha ? 1 : 0);
|
|
||||||
|
|
||||||
int[] bits = new int[bands];
|
|
||||||
for (int i = 0; i < bands; i++) {
|
|
||||||
bits[i] = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
ColorModel cm = pAlpha ?
|
|
||||||
new ComponentColorModel(cs, bits, true, true, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE) :
|
|
||||||
new ComponentColorModel(cs, bits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
|
|
||||||
|
|
||||||
byte[] pixels = new byte[length * bands];
|
|
||||||
|
|
||||||
// TODO: If we do multiple dispatches (one per line, typically), we could provide listener
|
|
||||||
// feedback. But it's currently a lot slower than fetching all the pixels in one go.
|
|
||||||
// TODO: handle more generic cases if profile is not CMYK
|
|
||||||
// TODO: Test "ACMYK"
|
|
||||||
pImage.dispatchImage(0, 0, size.width, size.height, pAlpha ? "ACMYK" : "CMYK", pixels);
|
|
||||||
|
|
||||||
// Init databuffer with array, to avoid allocation of empty array
|
|
||||||
DataBuffer buffer = new DataBufferByte(pixels, pixels.length);
|
|
||||||
|
|
||||||
// TODO: build array from bands variable, here it just works for CMYK
|
|
||||||
// The values has not been tested with an alpha picture actually...
|
|
||||||
int[] bandOffsets = pAlpha ? new int[] {0, 1, 2, 3, 4} : new int[] {0, 1, 2, 3};
|
|
||||||
|
|
||||||
WritableRaster raster =
|
|
||||||
Raster.createInterleavedRaster(buffer, size.width, size.height,
|
|
||||||
size.width * bands, bands, bandOffsets, LOCATION_UPPER_LEFT);
|
|
||||||
|
|
||||||
return new BufferedImage(cm, raster, pAlpha, null);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -55,9 +55,7 @@
|
|||||||
package com.twelvemonkeys.image;
|
package com.twelvemonkeys.image;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.*;
|
||||||
import java.awt.geom.Point2D;
|
|
||||||
import java.awt.geom.Rectangle2D;
|
|
||||||
import java.awt.image.*;
|
import java.awt.image.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -103,15 +101,6 @@ import java.awt.image.*;
|
|||||||
* BufferedImage scaled = new ResampleOp(w, h).filter(temp, null);
|
* BufferedImage scaled = new ResampleOp(w, h).filter(temp, null);
|
||||||
* </pre></blockquote>
|
* </pre></blockquote>
|
||||||
* <p>
|
* <p>
|
||||||
* For maximum performance, this class will use native code, through
|
|
||||||
* <a href="http://www.yeo.id.au/jmagick/">JMagick</a>, when available.
|
|
||||||
* Otherwise, the class will silently fall back to pure Java mode.
|
|
||||||
* Native code may be disabled globally, by setting the system property
|
|
||||||
* {@code com.twelvemonkeys.image.accel} to {@code false}.
|
|
||||||
* To allow debug of the native code, set the system property
|
|
||||||
* {@code com.twelvemonkeys.image.magick.debug} to {@code true}.
|
|
||||||
* </p>
|
|
||||||
* <p>
|
|
||||||
* This {@code BufferedImageOp} is based on C example code found in
|
* This {@code BufferedImageOp} is based on C example code found in
|
||||||
* <a href="http://www.acm.org/tog/GraphicsGems/">Graphics Gems III</a>,
|
* <a href="http://www.acm.org/tog/GraphicsGems/">Graphics Gems III</a>,
|
||||||
* Filtered Image Rescaling, by Dale Schumacher (with additional improvments by
|
* Filtered Image Rescaling, by Dale Schumacher (with additional improvments by
|
||||||
@ -139,9 +128,6 @@ import java.awt.image.*;
|
|||||||
// TODO: Consider using AffineTransformOp for more operations!?
|
// TODO: Consider using AffineTransformOp for more operations!?
|
||||||
public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
||||||
|
|
||||||
// NOTE: These MUST correspond to ImageMagick filter types, for the
|
|
||||||
// MagickAccelerator to work consistently (see magick.FilterType).
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Undefined interpolation, filter method will use default filter.
|
* Undefined interpolation, filter method will use default filter.
|
||||||
*/
|
*/
|
||||||
@ -295,11 +281,10 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
|||||||
new Value(KEY_RESAMPLE_INTERPOLATION, "Blackman-Sinc", FILTER_BLACKMAN_SINC);
|
new Value(KEY_RESAMPLE_INTERPOLATION, "Blackman-Sinc", FILTER_BLACKMAN_SINC);
|
||||||
|
|
||||||
// Member variables
|
// Member variables
|
||||||
// Package access, to allow access from MagickAccelerator
|
private final int width;
|
||||||
int width;
|
private final int height;
|
||||||
int height;
|
|
||||||
|
|
||||||
int filterType;
|
private final int filterType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RendereingHints.Key implementation, works only with Value values.
|
* RendereingHints.Key implementation, works only with Value values.
|
||||||
@ -547,16 +532,6 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
|||||||
// Fall through
|
// Fall through
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to use native ImageMagick code
|
|
||||||
BufferedImage result = MagickAccelerator.filter(this, input, output);
|
|
||||||
if (result != null) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, continue in pure Java mode
|
|
||||||
|
|
||||||
// TODO: What if output != null and wrong size? Create new? Render on only a part? Document?
|
|
||||||
|
|
||||||
// If filter type != POINT or BOX and input has IndexColorModel, convert
|
// If filter type != POINT or BOX and input has IndexColorModel, convert
|
||||||
// to true color, with alpha reflecting that of the original color model.
|
// to true color, with alpha reflecting that of the original color model.
|
||||||
BufferedImage temp;
|
BufferedImage temp;
|
||||||
@ -571,7 +546,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
|||||||
|
|
||||||
// Create or convert output to a suitable image
|
// Create or convert output to a suitable image
|
||||||
// TODO: OPTIMIZE: Don't really need to convert all types to same as input
|
// TODO: OPTIMIZE: Don't really need to convert all types to same as input
|
||||||
result = output != null && temp.getType() != BufferedImage.TYPE_CUSTOM ? /*output*/ ImageUtil.toBuffered(output, temp.getType()) : createCompatibleDestImage(temp, null);
|
BufferedImage result = output != null && temp.getType() != BufferedImage.TYPE_CUSTOM ? /*output*/ ImageUtil.toBuffered(output, temp.getType()) : createCompatibleDestImage(temp, null);
|
||||||
|
|
||||||
resample(temp, result, filter);
|
resample(temp, result, filter);
|
||||||
|
|
||||||
@ -1280,12 +1255,12 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
|||||||
/*
|
/*
|
||||||
* image rescaling routine
|
* image rescaling routine
|
||||||
*/
|
*/
|
||||||
class Contributor {
|
static class Contributor {
|
||||||
int pixel;
|
int pixel;
|
||||||
double weight;
|
double weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ContributorList {
|
static class ContributorList {
|
||||||
int n;/* number of contributors (may be < p.length) */
|
int n;/* number of contributors (may be < p.length) */
|
||||||
Contributor[] p;/* pointer to list of contributions */
|
Contributor[] p;/* pointer to list of contributions */
|
||||||
}
|
}
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
TODO:
|
|
||||||
Remove compile-time dependency on JMagick:
|
|
||||||
- Extract interface for MagickAccelerator
|
|
||||||
- Move implementation to separate module
|
|
||||||
- Instantiate impl via reflection
|
|
||||||
DONE:
|
|
Loading…
x
Reference in New Issue
Block a user