mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-04 20:15:28 -04:00
Rewrote handling of JPEG 2000 icons. Now returns blank image with correct dimensions + issues warning if can't be read, instead of exception.
Added quick fix conversion/reading for OS X using sips command line. Updated test cases.
This commit is contained in:
parent
905a3da97b
commit
c006f22ac2
@ -184,7 +184,7 @@ public final class ICNSImageReader extends ImageReaderBase {
|
||||
|
||||
// Special handling of PNG/JPEG 2000 icons
|
||||
if (resource.isForeignFormat()) {
|
||||
return readForeignFormat(param, resource);
|
||||
return readForeignFormat(imageIndex, param, resource);
|
||||
}
|
||||
|
||||
return readICNSFormat(imageIndex, param, resource);
|
||||
@ -418,10 +418,11 @@ public final class ICNSImageReader extends ImageReaderBase {
|
||||
return null;
|
||||
}
|
||||
|
||||
private BufferedImage readForeignFormat(final ImageReadParam param, final IconResource resource) throws IOException {
|
||||
private BufferedImage readForeignFormat(int imageIndex, final ImageReadParam param, final IconResource resource) throws IOException {
|
||||
ImageInputStream stream = ImageIO.createImageInputStream(IIOUtil.createStreamAdapter(imageInput, resource.length));
|
||||
|
||||
try {
|
||||
// Try first using ImageIO
|
||||
Iterator<ImageReader> readers = ImageIO.getImageReaders(stream);
|
||||
|
||||
while (readers.hasNext()) {
|
||||
@ -436,20 +437,38 @@ public final class ICNSImageReader extends ImageReaderBase {
|
||||
stream.seek(0);
|
||||
}
|
||||
else {
|
||||
stream.close();
|
||||
stream = ImageIO.createImageInputStream(IIOUtil.createStreamAdapter(imageInput, resource.length));
|
||||
}
|
||||
}
|
||||
finally {
|
||||
reader.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
String format = getForeignFormat(stream);
|
||||
|
||||
// OS X quick fix
|
||||
if ("JPEG 2000".equals(format) && SipsJP2Reader.isAvailable()) {
|
||||
SipsJP2Reader reader = new SipsJP2Reader();
|
||||
reader.setInput(stream);
|
||||
BufferedImage image = reader.read(0, param);
|
||||
|
||||
if (image != null) {
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
// There's no JPEG 2000 reader installed in ImageIO by default (requires JAI ImageIO installed).
|
||||
// The current implementation is correct, but a bit harsh maybe..? Other options:
|
||||
// TODO: Return blank icon + issue warning? We know the image dimensions, we just can't read the data...
|
||||
// TODO: Pretend it's not in the stream + issue warning?
|
||||
// TODO: Create JPEG 2000 reader..? :-P
|
||||
throw new IIOException(String.format(
|
||||
// Return blank icon + issue warning. We know the image dimensions, we just can't read the data.
|
||||
processWarningOccurred(String.format(
|
||||
"Cannot read %s format in type '%s' icon (no reader; installed: %s)",
|
||||
getForeignFormat(stream), ICNSUtil.intToStr(resource.type), Arrays.toString(ImageIO.getReaderFormatNames())
|
||||
format, ICNSUtil.intToStr(resource.type), Arrays.toString(ImageIO.getReaderFormatNames())
|
||||
));
|
||||
|
||||
Dimension size = resource.size();
|
||||
|
||||
return getDestination(param, getImageTypes(imageIndex), size.width, size.height);
|
||||
}
|
||||
finally {
|
||||
stream.close();
|
||||
@ -458,6 +477,7 @@ public final class ICNSImageReader extends ImageReaderBase {
|
||||
|
||||
private String getForeignFormat(final ImageInputStream stream) throws IOException {
|
||||
byte[] magic = new byte[12]; // Length of JPEG 2000 magic
|
||||
|
||||
try {
|
||||
stream.readFully(magic);
|
||||
}
|
||||
@ -466,6 +486,7 @@ public final class ICNSImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
String format;
|
||||
|
||||
if (Arrays.equals(ICNS.PNG_MAGIC, magic)) {
|
||||
format = "PNG";
|
||||
}
|
||||
@ -575,7 +596,7 @@ public final class ICNSImageReader extends ImageReaderBase {
|
||||
catch (IOException e) {
|
||||
imagesSkipped++;
|
||||
if (e.getMessage().contains("JPEG 2000")) {
|
||||
// System.err.printf("%s: %s\n", input, e.getMessage());
|
||||
System.err.printf("%s: %s\n", input, e.getMessage());
|
||||
}
|
||||
else {
|
||||
System.err.printf("%s: ", input);
|
||||
|
@ -216,7 +216,7 @@ final class IconResource {
|
||||
}
|
||||
|
||||
public boolean isUnknownType() {
|
||||
// These should simply be skipped
|
||||
// Unknown types should simply be skipped when reading
|
||||
switch (type) {
|
||||
case ICNS.ICON:
|
||||
case ICNS.ICN_:
|
||||
@ -290,12 +290,14 @@ final class IconResource {
|
||||
}
|
||||
|
||||
public boolean isForeignFormat() {
|
||||
// Recent entries contains full JPEG 2000 or PNG streams
|
||||
switch (type) {
|
||||
case ICNS.ic08:
|
||||
case ICNS.ic09:
|
||||
case ICNS.ic10:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -310,6 +312,7 @@ final class IconResource {
|
||||
}
|
||||
|
||||
private boolean isEqual(IconResource other) {
|
||||
// This isn't strictly true, as resource must reside in same stream as well, but good enough for now
|
||||
return start == other.start && type == other.type && length == other.length;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright (c) 2011, 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 "TwelveMonkeys" 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 OWNER 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.imageio.plugins.icns;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.io.FileUtil;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.*;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* QuickFix for OS X (where ICNS are most useful) and JPEG 2000.
|
||||
* Dumps the stream to disk and converts using sips command line tool:
|
||||
* {@code sips -s format png <temp>}.
|
||||
* Reads image back using ImageIO and known format (png).
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: JPEG2000Reader.java,v 1.0 25.11.11 14:17 haraldk Exp$
|
||||
*/
|
||||
final class SipsJP2Reader {
|
||||
|
||||
private static final File SIPS_COMMAND = new File("/usr/bin/sips");
|
||||
private static final boolean SIPS_EXISTS_AND_EXECUTES = existsAndExecutes(SIPS_COMMAND);
|
||||
|
||||
private static boolean existsAndExecutes(final File cmd) {
|
||||
try {
|
||||
return cmd.exists() && cmd.canExecute();
|
||||
}
|
||||
catch (SecurityException ignore) {
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private ImageInputStream input;
|
||||
|
||||
public BufferedImage read(final int imageIndex, final ImageReadParam param) throws IOException {
|
||||
// Test if we have sips before dumping to be fail-fast
|
||||
if (SIPS_EXISTS_AND_EXECUTES) {
|
||||
File tempFile = dumpToFile(input);
|
||||
|
||||
if (convertToPNG(tempFile)) {
|
||||
ImageInputStream stream = ImageIO.createImageInputStream(tempFile);
|
||||
Iterator<ImageReader> readers = ImageIO.getImageReaders(stream);
|
||||
|
||||
while (readers.hasNext()) {
|
||||
ImageReader reader = readers.next();
|
||||
reader.setInput(stream);
|
||||
|
||||
try {
|
||||
return reader.read(imageIndex, param);
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
if (stream.getFlushedPosition() <= 0) {
|
||||
stream.seek(0);
|
||||
}
|
||||
else {
|
||||
stream.close();
|
||||
stream = ImageIO.createImageInputStream(tempFile);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
reader.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setInput(final ImageInputStream input) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
private static boolean convertToPNG(final File tempFile) throws IIOException {
|
||||
try {
|
||||
Process process = Runtime.getRuntime().exec(buildCommand(SIPS_COMMAND, tempFile));
|
||||
|
||||
// NOTE: sips return status is 0, even if error, need to check error message
|
||||
int status = process.waitFor();
|
||||
String message = checkErrorMessage(process);
|
||||
|
||||
if (status == 0 && message == null) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
throw new IOException(message);
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new IIOException("Interrupted converting JPEG 2000 format", e);
|
||||
}
|
||||
catch (SecurityException e) {
|
||||
// Exec might need permissions in sandboxed environment
|
||||
throw new IIOException("Cannot convert JPEG 2000 format without file permissions", e);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new IIOException("Error converting JPEG 2000 format: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String checkErrorMessage(final Process process) throws IOException {
|
||||
InputStream stream = process.getErrorStream();
|
||||
|
||||
try {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
|
||||
String message = reader.readLine();
|
||||
|
||||
return message != null && message.startsWith("Error: ") ? message.substring(7) : null;
|
||||
}
|
||||
finally {
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static String[] buildCommand(final File sipsCommand, final File tempFile) {
|
||||
return new String[]{
|
||||
sipsCommand.getAbsolutePath(), "-s", "format", "png", tempFile.getAbsolutePath()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private static File dumpToFile(final ImageInputStream stream) throws IOException {
|
||||
File tempFile = File.createTempFile("imageio-icns-", ".png");
|
||||
tempFile.deleteOnExit();
|
||||
|
||||
FileOutputStream out = new FileOutputStream(tempFile);
|
||||
|
||||
try {
|
||||
FileUtil.copy(IIOUtil.createStreamAdapter(stream), out);
|
||||
}
|
||||
finally {
|
||||
out.close();
|
||||
}
|
||||
|
||||
return tempFile;
|
||||
}
|
||||
|
||||
static boolean isAvailable() {
|
||||
return SIPS_EXISTS_AND_EXECUTES;
|
||||
}
|
||||
}
|
@ -61,19 +61,19 @@ public class ICNSImageReaderTest extends ImageReaderAbstractTestCase {
|
||||
new Dimension(32, 32), // 24 bit + 8 bit mask
|
||||
new Dimension(48, 48), // 24 bit + 8 bit mask
|
||||
new Dimension(128, 128) // 24 bit + 8 bit mask
|
||||
//, new Dimension(256, 256), // JPEG 2000 ic08, not readable without JAI or other JPEG 2000 support
|
||||
// new Dimension(512, 512) // JPEG 2000 ic09
|
||||
, new Dimension(256, 256), // JPEG 2000 ic08
|
||||
new Dimension(512, 512) // JPEG 2000 ic09
|
||||
),
|
||||
new TestData(
|
||||
getClassLoaderResource("/icns/7zIcon.icns"), // Contains the icnV resource, that isn't an icon
|
||||
new Dimension(16, 16), // 24 bit + 8 bit mask
|
||||
new Dimension(32, 32), // 24 bit + 8 bit mask
|
||||
new Dimension(128, 128) // 24 bit + 8 bit mask
|
||||
//, new Dimension(256, 256), // JPEG 2000 ic08
|
||||
// new Dimension(512, 512) // JPEG 2000 ic09
|
||||
, new Dimension(256, 256), // JPEG 2000 ic08
|
||||
new Dimension(512, 512) // JPEG 2000 ic09
|
||||
),
|
||||
new TestData(
|
||||
getClassLoaderResource("/icns/appStore.icns"), // Contains the 'TOC ' and icnV resources
|
||||
getClassLoaderResource("/icns/appStore.icns"), // Contains the 'TOC ' and icnV resources + PNGs in ic08-10
|
||||
new Dimension(16, 16), // 24 bit + 8 bit mask
|
||||
new Dimension(32, 32), // 24 bit + 8 bit mask
|
||||
new Dimension(128, 128), // 24 bit + 8 bit mask
|
||||
@ -82,7 +82,7 @@ public class ICNSImageReaderTest extends ImageReaderAbstractTestCase {
|
||||
new Dimension(1024, 1024) // PNG ic10
|
||||
),
|
||||
new TestData(
|
||||
getClassLoaderResource("/icns/XLW.icns"), // No 8 bit mask for 16x16 & 32x32, test fall back to 1 bit mask
|
||||
getClassLoaderResource("/icns/XLW.icns"), // No 8 bit mask for 16x16 & 32x32, fall back to 1 bit mask
|
||||
new Dimension(16, 16), // 1 bit + 1 bit mask
|
||||
new Dimension(16, 16), new Dimension(16, 16), // 4 bit CMAP, 8 bit CMAP (no 8 bit mask)
|
||||
new Dimension(32, 32), // 1 bit + 1 bit mask
|
||||
|
Loading…
x
Reference in New Issue
Block a user