From 5e5d530498710abf9e3b56e4791b2463746f81ef Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Mon, 7 Oct 2024 20:24:43 +0200 Subject: [PATCH] RIP MagickAccelerator & friends, remove JMagick dependency --- common/common-image/pom.xml | 7 - .../java/com/twelvemonkeys/image/Magick.java | 55 -- .../image/MagickAccelerator.java | 187 ------ .../com/twelvemonkeys/image/MagickUtil.java | 621 ------------------ .../com/twelvemonkeys/image/ResampleOp.java | 39 +- common/common-image/todo.txt | 6 - 6 files changed, 7 insertions(+), 908 deletions(-) delete mode 100755 common/common-image/src/main/java/com/twelvemonkeys/image/Magick.java delete mode 100755 common/common-image/src/main/java/com/twelvemonkeys/image/MagickAccelerator.java delete mode 100755 common/common-image/src/main/java/com/twelvemonkeys/image/MagickUtil.java delete mode 100644 common/common-image/todo.txt diff --git a/common/common-image/pom.xml b/common/common-image/pom.xml index a1973075..d3042272 100644 --- a/common/common-image/pom.xml +++ b/common/common-image/pom.xml @@ -28,13 +28,6 @@ common-io - - jmagick - jmagick - 6.6.9 - true - provided - diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/Magick.java b/common/common-image/src/main/java/com/twelvemonkeys/image/Magick.java deleted file mode 100755 index b348e22e..00000000 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/Magick.java +++ /dev/null @@ -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 Harald Kuhr - * @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() {} -} diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/MagickAccelerator.java b/common/common-image/src/main/java/com/twelvemonkeys/image/MagickAccelerator.java deleted file mode 100755 index ed0753e9..00000000 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/MagickAccelerator.java +++ /dev/null @@ -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. - *

- * 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 - *

- * - * @author Harald Kuhr - * @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(); - } - } - } -} \ No newline at end of file diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/MagickUtil.java b/common/common-image/src/main/java/com/twelvemonkeys/image/MagickUtil.java deleted file mode 100755 index a5a316ea..00000000 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/MagickUtil.java +++ /dev/null @@ -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. - *

- * 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. :-) - *

- * - * @author Harald Kuhr - * @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}. - *

- * The conversion depends on {@code pImage}'s {@code ImageType}: - *

- *
- *
{@code ImageType.BilevelType}
- *
{@code BufferedImage} of type {@code TYPE_BYTE_BINARY}
- * - *
{@code ImageType.GrayscaleType}
- *
{@code BufferedImage} of type {@code TYPE_BYTE_GRAY}
- *
{@code ImageType.GrayscaleMatteType}
- *
{@code BufferedImage} of type {@code TYPE_USHORT_GRAY}
- * - *
{@code ImageType.PaletteType}
- *
{@code BufferedImage} of type {@code TYPE_BYTE_BINARY} (for images - * with a palette of <= 16 colors) or {@code TYPE_BYTE_INDEXED}
- *
{@code ImageType.PaletteMatteType}
- *
{@code BufferedImage} of type {@code TYPE_BYTE_BINARY} (for images - * with a palette of <= 16 colors) or {@code TYPE_BYTE_INDEXED}
- * - *
{@code ImageType.TrueColorType}
- *
{@code BufferedImage} of type {@code TYPE_3BYTE_BGR}
- *
{@code ImageType.TrueColorPaletteType}
- *
{@code BufferedImage} of type {@code TYPE_4BYTE_ABGR}
- *
- * - * @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}. - *

- * The conversion depends on {@code pImage}'s {@code ColorModel}: - *

- *
- *
{@code IndexColorModel} with 1 bit b/w
- *
{@code MagickImage} of type {@code ImageType.BilevelType}
- *
{@code IndexColorModel} > 1 bit,
- *
{@code MagickImage} of type {@code ImageType.PaletteType} - * or {@code MagickImage} of type {@code ImageType.PaletteMatteType} - * depending on ColorModel.getAlpha()
- * - *
{@code ColorModel.getColorSpace().getType() == ColorSpace.TYPE_GRAY}
- *
{@code MagickImage} of type {@code ImageType.GrayscaleType} - * or {@code MagickImage} of type {@code ImageType.GrayscaleMatteType} - * depending on ColorModel.getAlpha()
- * - *
{@code ColorModel.getColorSpace().getType() == ColorSpace.TYPE_RGB}
- *
{@code MagickImage} of type {@code ImageType.TrueColorType} - * or {@code MagickImage} of type {@code ImageType.TrueColorPaletteType}
- *
- * - * @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); - - } -} diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/ResampleOp.java b/common/common-image/src/main/java/com/twelvemonkeys/image/ResampleOp.java index d61d220d..0ddc7eab 100644 --- a/common/common-image/src/main/java/com/twelvemonkeys/image/ResampleOp.java +++ b/common/common-image/src/main/java/com/twelvemonkeys/image/ResampleOp.java @@ -55,9 +55,7 @@ package com.twelvemonkeys.image; import java.awt.*; -import java.awt.geom.AffineTransform; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; +import java.awt.geom.*; import java.awt.image.*; /** @@ -103,15 +101,6 @@ import java.awt.image.*; * BufferedImage scaled = new ResampleOp(w, h).filter(temp, null); * *

- * For maximum performance, this class will use native code, through - * JMagick, 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}. - *

- *

* This {@code BufferedImageOp} is based on C example code found in * Graphics Gems III, * 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!? 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. */ @@ -295,11 +281,10 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ { new Value(KEY_RESAMPLE_INTERPOLATION, "Blackman-Sinc", FILTER_BLACKMAN_SINC); // Member variables - // Package access, to allow access from MagickAccelerator - int width; - int height; + private final int width; + private final int height; - int filterType; + private final int filterType; /** * RendereingHints.Key implementation, works only with Value values. @@ -547,16 +532,6 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ { // 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 // to true color, with alpha reflecting that of the original color model. BufferedImage temp; @@ -571,7 +546,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ { // Create or convert output to a suitable image // 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); @@ -1280,12 +1255,12 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ { /* * image rescaling routine */ - class Contributor { + static class Contributor { int pixel; double weight; } - class ContributorList { + static class ContributorList { int n;/* number of contributors (may be < p.length) */ Contributor[] p;/* pointer to list of contributions */ } diff --git a/common/common-image/todo.txt b/common/common-image/todo.txt deleted file mode 100644 index c8ed52d1..00000000 --- a/common/common-image/todo.txt +++ /dev/null @@ -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: \ No newline at end of file