Skip to content

IMAGING-138 Provide some Ant tasks to perform image conversions #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: trunk
Choose a base branch
from
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -207,6 +207,11 @@
<version>2.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.ant</groupId>
<artifactId>ant</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>

<reporting>
342 changes: 342 additions & 0 deletions src/main/java/org/apache/commons/imaging/ImageConverter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,342 @@
package org.apache.commons.imaging;

import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import org.apache.commons.imaging.formats.dcx.DcxImageParser;
import org.apache.commons.imaging.formats.icns.IcnsImageParser;
import org.apache.commons.imaging.formats.ico.IcoImageParser;
import org.apache.commons.imaging.formats.tiff.TiffImageParser;

/**
* Image converter
*
*/
public class ImageConverter {

/**
* index of the source image to use during the conversion when the
* destination format supports a single image per data source, ignored when
* an hint to compute this index dynamically is enabled
*/
private int sourceImageIndex;

private boolean indexOfSmallestSourceImageComputationEnabled;

private boolean indexOfBiggestSourceImageComputationEnabled;

/**
* indicates whether several images should be computed from one source image
* when the destination format supports more than one image per data source.
* The source image is then copied and rescaled several times. Ignored when
* the destination format is not ICNS
*/
private boolean sourceImageDuplicationAndRescaleEnabled;

public ImageConverter() {
super();
this.sourceImageIndex = 0;
}

public int getSourceImageIndex() {
return sourceImageIndex;
}

public void setSourceImageIndex(final int sourceImageIndex) {
this.sourceImageIndex = sourceImageIndex;
}

public boolean isIndexOfSmallestSourceImageComputationEnabled() {
return indexOfSmallestSourceImageComputationEnabled;
}

public void setIndexOfSmallestSourceImageComputationEnabled(
boolean indexOfSmallestSourceImageComputationEnabled) {
this.indexOfSmallestSourceImageComputationEnabled = indexOfSmallestSourceImageComputationEnabled;
}

public boolean isIndexOfBiggestSourceImageComputationEnabled() {
return indexOfBiggestSourceImageComputationEnabled;
}

public void setIndexOfBiggestSourceImageComputationEnabled(
boolean indexOfBiggestSourceImageComputationEnabled) {
this.indexOfBiggestSourceImageComputationEnabled = indexOfBiggestSourceImageComputationEnabled;
}

public boolean isSourceImageDuplicationAndRescaleEnabled() {
return sourceImageDuplicationAndRescaleEnabled;
}

public void setSourceImageDuplicationAndRescaleEnabled(
boolean sourceImageDuplicationAndRescaleEnabled) {
this.sourceImageDuplicationAndRescaleEnabled = sourceImageDuplicationAndRescaleEnabled;
}

/**
* Converts the source image file into the format of the destination image
* file
*
* @param srcFile
* source image file
* @param dstFile
* destination image file
* @throws IOException
* In the event that the destination file cannot be (re)created
* or an I/O error occurs
* @throws ImageReadException
* In the event that the source image file cannot be read
* @throws ImageWriteException
* In the event that the destination image file cannot be
* written (for example if the destination format does not
* support the size or the palette of the source image)
* @throws IllegalArgumentException
* In the event that at least one extension is invalid, when the
* source file does not exist
*/
public void convertImage(final File srcFile, final File dstFile)
throws IOException, ImageReadException, ImageWriteException,
IllegalArgumentException {
if (!srcFile.exists()) {
throw new IllegalArgumentException("The source file "
+ srcFile.getAbsolutePath() + " does not exist");
}
final int srcFileExtIndex = srcFile.getName().lastIndexOf('.');
final String srcFileExt;
if (srcFileExtIndex != -1
&& srcFileExtIndex + 1 < srcFile.getName().length()) {
srcFileExt = srcFile.getName().substring(srcFileExtIndex);
} else {
srcFileExt = null;
}
final int dstFileExtIndex = dstFile.getName().lastIndexOf('.');
final String dstFileExt;
if (dstFileExtIndex != -1
&& dstFileExtIndex + 1 < dstFile.getName().length()) {
dstFileExt = dstFile.getName().substring(dstFileExtIndex);
} else {
dstFileExt = null;
}

checkExtensionsValidity(srcFileExt, dstFileExt);

List<BufferedImage> srcImgs = Imaging.getAllBufferedImages(srcFile);
if (dstFile.exists() && !dstFile.delete()) {
throw new IllegalArgumentException("The destination file "
+ dstFile.getAbsolutePath()
+ " already exists and cannot be deleted");
}
dstFile.createNewFile();
if (srcImgs != null && !srcImgs.isEmpty()) {
BufferedOutputStream bos = null;
try {
bos = new BufferedOutputStream(new FileOutputStream(dstFile));
doConvertImage(srcImgs, bos, dstFileExt);
} finally {
if (bos != null) {
bos.close();
}
}
}
}

protected void doConvertImage(final List<BufferedImage> srcImgs,
final OutputStream os, final String dstExt)
throws ImageWriteException, IOException, IllegalArgumentException {
final ImageParser dstParser = getImageParser(dstExt);
if (srcImgs.size() == 1) {
if (supportsMultipleImagesWithinSingleDataSource(dstParser)) {
if (sourceImageDuplicationAndRescaleEnabled
&& dstParser.getClass() == IcnsImageParser.class) {
BufferedImage srcImg = srcImgs.get(0);
List<BufferedImage> altSrcImgs = new ArrayList<BufferedImage>();
final int[] sizes = new int[]{16, 32, 48, 128};
for (int size : sizes) {
if (srcImg.getWidth() == size && srcImg.getHeight() == size) {
altSrcImgs.add(srcImg);
} else {
BufferedImage rescaledImg = new BufferedImage(size,
size, BufferedImage.TYPE_INT_ARGB);
AffineTransform transform = new AffineTransform();
transform.scale(size / (double) srcImg.getWidth(),
size / (double) srcImg.getHeight());
AffineTransformOp scaleOp = new AffineTransformOp(
transform, AffineTransformOp.TYPE_BICUBIC);
scaleOp.filter(srcImg, rescaledImg);
altSrcImgs.add(rescaledImg);
}
}
dstParser.writeImages(altSrcImgs, os, null);
} else {
dstParser.writeImage(srcImgs.get(0), os, null);
}
} else {
dstParser.writeImage(srcImgs.get(0), os, null);
}
} else {
if (supportsMultipleImagesWithinSingleDataSource(dstParser)) {
dstParser.writeImages(srcImgs, os, null);
} else {
if (indexOfSmallestSourceImageComputationEnabled) {
int indexOfSmallestSourceImage = 0;
int areaOfSmallestSourceImage = srcImgs.get(0).getWidth()
* srcImgs.get(0).getHeight();
for (int srcIndex = 1; srcIndex < srcImgs.size(); srcIndex++) {
int areaOfSourceImage = srcImgs.get(srcIndex)
.getWidth() * srcImgs.get(srcIndex).getHeight();
if (areaOfSourceImage < areaOfSmallestSourceImage) {
areaOfSmallestSourceImage = areaOfSourceImage;
indexOfSmallestSourceImage = srcIndex;
}
}
dstParser.writeImage(
srcImgs.get(indexOfSmallestSourceImage), os, null);
} else {
if (indexOfBiggestSourceImageComputationEnabled) {
int indexOfBiggestSourceImage = 0;
int areaOfBiggestSourceImage = srcImgs.get(0)
.getWidth() * srcImgs.get(0).getHeight();
for (int srcIndex = 1; srcIndex < srcImgs.size(); srcIndex++) {
int areaOfSourceImage = srcImgs.get(srcIndex)
.getWidth()
* srcImgs.get(srcIndex).getHeight();
if (areaOfSourceImage > areaOfBiggestSourceImage) {
areaOfBiggestSourceImage = areaOfSourceImage;
indexOfBiggestSourceImage = srcIndex;
}
}
dstParser.writeImage(
srcImgs.get(indexOfBiggestSourceImage), os,
null);
} else {
if (sourceImageIndex < 0
|| srcImgs.size() <= sourceImageIndex) {
throw new IllegalArgumentException(
"Impossible to read an image at index "
+ sourceImageIndex);
} else {
dstParser.writeImage(srcImgs.get(sourceImageIndex),
os, null);
}
}
}
}
}
}

/**
* Checks whether the extension of the source and the extension of the
* destination are valid and distinct
*
* @param srcExt
* extension of the source
* @param dstExt
* extension of the destination
* @throws IllegalArgumentException
* In the event that one of them is invalid or both are two
* extensions of the same format
*/
protected void checkExtensionsValidity(final String srcExt,
final String dstExt) throws IllegalArgumentException {
if (srcExt == null || srcExt.length() == 0) {
throw new IllegalArgumentException("The source has no extension");
}
if (!Imaging.hasImageFileExtension(srcExt)) {
throw new IllegalArgumentException(srcExt
+ " is not an image file extension");
}
if (dstExt == null || dstExt.length() == 0) {
throw new IllegalArgumentException(
"The destination has no extension");
}
if (!Imaging.hasImageFileExtension(dstExt)) {
throw new IllegalArgumentException(dstExt
+ " is not an image file extension");
}
final ImageParser srcParser = getImageParser(srcExt);
final ImageParser dstParser = getImageParser(dstExt);
if (srcParser.getClass() == dstParser.getClass()) {
throw new IllegalArgumentException("No conversion needed");
}
}

/**
* Converts the source image stream into the format of the destination image
* stream
*
* @param is
* source image stream
* @param srcExt
* extension of the source image
* @param os
* destination image stream
* @param dstExt
* extension of the destination image
* @throws IOException
* In the event of an I/O error
* @throws ImageReadException
* In the event that the source image stream cannot be read
* @throws ImageWriteException
* In the event that the destination image stream cannot be
* written (for example if the destination format does not
* support the size or the palette of the source image)
* @throws IllegalArgumentException
* In the event that at least one extension is invalid
*/
public void convertImage(final InputStream is, final String srcExt,
final OutputStream os, final String dstExt) throws IOException,
ImageReadException, ImageWriteException, IllegalArgumentException {
checkExtensionsValidity(srcExt, dstExt);
List<BufferedImage> srcImgs = Imaging.getAllBufferedImages(is, srcExt);
if (srcImgs != null && !srcImgs.isEmpty()) {
doConvertImage(srcImgs, os, dstExt);
}
}

private boolean supportsMultipleImagesWithinSingleDataSource(
final ImageParser imageParser) {
final Class imageParserClass = imageParser.getClass();
return /*imageParserClass == DcxImageParser.class
|| */imageParserClass == IcnsImageParser.class/*
|| imageParserClass == IcoImageParser.class
|| imageParserClass == TiffImageParser.class*/;
}

/**
* Returns the image parser of the file
*
* @param filename
* image filename
* @return image parser of the image if any, otherwise <code>null</code>
*/
private ImageParser getImageParser(String filename) {
if (filename == null) {
return null;
}

filename = filename.toLowerCase(Locale.ENGLISH);

final ImageParser[] imageParsers = ImageParser.getAllImageParsers();
for (final ImageParser imageParser : imageParsers) {
final String[] exts = imageParser.getAcceptedExtensions();

for (final String ext : exts) {
if (filename.endsWith(ext.toLowerCase(Locale.ENGLISH))) {
return imageParser;
}
}
}

return null;
}
}
101 changes: 101 additions & 0 deletions src/main/java/org/apache/commons/imaging/ImageConverterTask.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.apache.commons.imaging;

import java.io.File;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;

/**
* Ant task of image conversion
*
*/
public class ImageConverterTask extends Task {

/**path of the source image*/
private String src;

/**path of the destination image*/
private String dst;

/**image converter*/
private final ImageConverter delegate;

/**
* Constructor
*/
public ImageConverterTask() {
super();
this.delegate = new ImageConverter();
}

@Override
public void execute() throws BuildException {
if (src == null) {
throw new BuildException("The src attribute cannot be null");
}
if (dst == null) {
throw new BuildException("The dst attribute cannot be null");
}
final File srcFile = new File(getProject().getBaseDir(), src);
final File dstFile = new File(getProject().getBaseDir(), dst);
try {
this.delegate.convertImage(srcFile, dstFile);
} catch (Throwable t) {
throw new BuildException("The conversion of the image file " + src
+ " failed", t, getLocation());
}
}

/**
* Sets the path of the destination image
*
* @param dst
*/
public void setDst(String dst) {
this.dst = dst;
}

/**
* Sets the path of the source image
*
* @param src
*/
public void setSrc(String src) {
this.src = src;
}

public void setSourceImageIndex(final int sourceImageIndex) {
this.delegate.setSourceImageIndex(sourceImageIndex);
}

public void setIndexOfSmallestSourceImageComputationEnabled(
boolean indexOfSmallestSourceImageComputationEnabled) {
this.delegate.setIndexOfSmallestSourceImageComputationEnabled(indexOfSmallestSourceImageComputationEnabled);
}

public void setIndexOfBiggestSourceImageComputationEnabled(
boolean indexOfBiggestSourceImageComputationEnabled) {
this.delegate.setIndexOfBiggestSourceImageComputationEnabled(indexOfBiggestSourceImageComputationEnabled);
}

public void setSourceImageDuplicationAndRescaleEnabled(
boolean sourceImageDuplicationAndRescaleEnabled) {
this.delegate.setSourceImageDuplicationAndRescaleEnabled(sourceImageDuplicationAndRescaleEnabled);
}
}
1,998 changes: 1,083 additions & 915 deletions src/main/java/org/apache/commons/imaging/ImageParser.java

Large diffs are not rendered by default.

3,039 changes: 1,597 additions & 1,442 deletions src/main/java/org/apache/commons/imaging/Imaging.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -250,6 +250,14 @@ public void writeImage(final BufferedImage src, final OutputStream os, Map<Strin
final PcxImageParser pcxImageParser = new PcxImageParser();
pcxImageParser.writeImage(src, bos, pcxParams);
}

@Override
public void writeImages(final List<BufferedImage> srcs,
final OutputStream os, final Map<String, Object> params)
throws ImageWriteException, IOException {
// TODO it should be implemented
super.writeImages(srcs, os, params);
}

/**
* Extracts embedded XML metadata as XML string.
Original file line number Diff line number Diff line change
@@ -283,7 +283,25 @@ public List<BufferedImage> getAllBufferedImages(final ByteSource byteSource)
return IcnsDecoder.decodeAllImages(icnsContents.icnsElements);
}

@Override
private IcnsType getIcnsTypeFromBufferedImage(BufferedImage src) throws ImageWriteException {
IcnsType imageType;
if (src.getWidth() == 16 && src.getHeight() == 16) {
imageType = IcnsType.ICNS_16x16_32BIT_IMAGE;
} else if (src.getWidth() == 32 && src.getHeight() == 32) {
imageType = IcnsType.ICNS_32x32_32BIT_IMAGE;
} else if (src.getWidth() == 48 && src.getHeight() == 48) {
imageType = IcnsType.ICNS_48x48_32BIT_IMAGE;
} else if (src.getWidth() == 128 && src.getHeight() == 128) {
imageType = IcnsType.ICNS_128x128_32BIT_IMAGE;
} else {
throw new ImageWriteException("Invalid/unsupported source width "
+ src.getWidth() + " and height " + src.getHeight());
}
return imageType;
}

@SuppressWarnings("resource")
@Override
public void writeImage(final BufferedImage src, final OutputStream os, Map<String, Object> params)
throws ImageWriteException, IOException {
// make copy of params; we'll clear keys as we consume them.
@@ -299,19 +317,7 @@ public void writeImage(final BufferedImage src, final OutputStream os, Map<Strin
throw new ImageWriteException("Unknown parameter: " + firstKey);
}

IcnsType imageType;
if (src.getWidth() == 16 && src.getHeight() == 16) {
imageType = IcnsType.ICNS_16x16_32BIT_IMAGE;
} else if (src.getWidth() == 32 && src.getHeight() == 32) {
imageType = IcnsType.ICNS_32x32_32BIT_IMAGE;
} else if (src.getWidth() == 48 && src.getHeight() == 48) {
imageType = IcnsType.ICNS_48x48_32BIT_IMAGE;
} else if (src.getWidth() == 128 && src.getHeight() == 128) {
imageType = IcnsType.ICNS_128x128_32BIT_IMAGE;
} else {
throw new ImageWriteException("Invalid/unsupported source width "
+ src.getWidth() + " and height " + src.getHeight());
}
IcnsType imageType = getIcnsTypeFromBufferedImage(src);

final BinaryOutputStream bos = new BinaryOutputStream(os,
ByteOrder.BIG_ENDIAN);
@@ -343,6 +349,78 @@ public void writeImage(final BufferedImage src, final OutputStream os, Map<Strin
}
}
}

@SuppressWarnings("resource")
@Override
public void writeImages(final List<BufferedImage> srcs,
final OutputStream os, Map<String, Object> params)
throws ImageWriteException, IOException {
if (!srcs.isEmpty()) {
if (srcs.size() == 1) {
writeImage(srcs.get(0), os, params);
} else {
// make copy of params; we'll clear keys as we consume them.
params = (params == null) ? new HashMap<String, Object>() : new HashMap<String, Object>(params);

// clear format key.
if (params.containsKey(PARAM_KEY_FORMAT)) {
params.remove(PARAM_KEY_FORMAT);
}

if (!params.isEmpty()) {
final Object firstKey = params.keySet().iterator().next();
throw new ImageWriteException("Unknown parameter: " + firstKey);
}

List<IcnsType> imageTypeList = new ArrayList<IcnsType>();
List<int[]> lengthOfIconsDataList = new ArrayList<int[]>();
int lengthOfFile = 4 + 4;// magic literal + length of file
for (BufferedImage src : srcs) {
IcnsType imageType = getIcnsTypeFromBufferedImage(src);
imageTypeList.add(imageType);
int lengthOfIconData = 4 + 4 + 4 * imageType.getWidth()
* imageType.getHeight();// icon type + length of data + icon data
int lengthOfMaskData = 4 + 4 + imageType.getWidth()
* imageType.getHeight();// icon type + length of data + "mask" data
lengthOfFile += lengthOfIconData + lengthOfMaskData;
lengthOfIconsDataList.add(new int[]{lengthOfIconData, lengthOfMaskData});
}
final BinaryOutputStream bos = new BinaryOutputStream(os,
ByteOrder.BIG_ENDIAN);
bos.write4Bytes(ICNS_MAGIC);
bos.write4Bytes(lengthOfFile);
int srcIndex = 0;
for (BufferedImage src : srcs) {
IcnsType imageType = imageTypeList.get(srcIndex);
bos.write4Bytes(imageType.getType());
int lengthOfIconData = lengthOfIconsDataList.get(srcIndex)[0];
bos.write4Bytes(lengthOfIconData);
for (int y = 0; y < src.getHeight(); y++) {
for (int x = 0; x < src.getWidth(); x++) {
final int argb = src.getRGB(x, y);
bos.write(0);
bos.write(argb >> 16);
bos.write(argb >> 8);
bos.write(argb);
}
}

final IcnsType maskType = IcnsType.find8BPPMaskType(imageType);
bos.write4Bytes(maskType.getType());
int lengthOfMaskData = lengthOfIconsDataList.get(srcIndex)[1];
bos.write4Bytes(lengthOfMaskData);
for (int y = 0; y < src.getHeight(); y++) {
for (int x = 0; x < src.getWidth(); x++) {
final int argb = src.getRGB(x, y);
bos.write(argb >> 24);
}
}

srcIndex++;
}
}
}
}

/**
* Extracts embedded XML metadata as XML string.
Original file line number Diff line number Diff line change
@@ -811,6 +811,14 @@ public void writeImage(final BufferedImage src, final OutputStream os, Map<Strin
}
}
}

@Override
public void writeImages(final List<BufferedImage> srcs,
final OutputStream os, final Map<String, Object> params)
throws ImageWriteException, IOException {
// TODO it should be implemented
super.writeImages(srcs, os, params);
}

/**
* Extracts embedded XML metadata as XML string.
Original file line number Diff line number Diff line change
@@ -733,5 +733,13 @@ public void writeImage(final BufferedImage src, final OutputStream os, final Map
throws ImageWriteException, IOException {
new TiffImageWriterLossy().writeImage(src, os, params);
}

@Override
public void writeImages(final List<BufferedImage> srcs,
final OutputStream os, final Map<String, Object> params)
throws ImageWriteException, IOException {
// TODO it should be implemented
super.writeImages(srcs, os, params);
}

}
3 changes: 3 additions & 0 deletions src/main/resources/org/apache/commons/imaging/antlib.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<antlib>
<taskdef name="convertimage" classname="org.apache.commons.imaging.ImageConverterTask" />
</antlib>