mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-04 03:55:28 -04:00
#83 ICNS write
This commit is contained in:
parent
b09a8a5dbe
commit
96901e020f
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2011, Harald Kuhr
|
||||
* Copyright (c) 2017, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -92,14 +92,28 @@ interface ICNS {
|
||||
/** 128×128 8-bit mask. */
|
||||
int t8mk = ('t' << 24) + ('8' << 16) + ('m' << 8) + 'k';
|
||||
|
||||
/** 256×256 JPEG 2000 or PNG icon (10.x+). */
|
||||
/** 16x16 JPEG2000 or PNG icon (10.7+). */
|
||||
int icp4 = ('i' << 24) + ('c' << 16) + ('p' << 8) + '4';
|
||||
/** 32x32 JPEG2000 or PNG icon (10.7+). */
|
||||
int icp5 = ('i' << 24) + ('c' << 16) + ('p' << 8) + '5';
|
||||
/** 64x64 JPEG2000 or PNG icon (10.7+). */
|
||||
int icp6 = ('i' << 24) + ('c' << 16) + ('p' << 8) + '6';
|
||||
/** 128x128 JPEG2000 or PNG icon (10.7+). */
|
||||
int ic07 = ('i' << 24) + ('c' << 16) + ('0' << 8) + '7';
|
||||
/** 256×256 JPEG 2000 or PNG icon (10.5+). */
|
||||
int ic08 = ('i' << 24) + ('c' << 16) + ('0' << 8) + '8';
|
||||
|
||||
/** 512×512 JPEG 2000 or PNG icon (10.x+). */
|
||||
/** 512×512 JPEG 2000 or PNG icon (10.5+). */
|
||||
int ic09 = ('i' << 24) + ('c' << 16) + ('0' << 8) + '9';
|
||||
|
||||
/** 1024×1024 PNG icon (10.7+). */
|
||||
/** 1024×1024 JPEG2000 or PNG icon (10.7+) OR 512x512@2x "retina" (10.8+). */
|
||||
int ic10 = ('i' << 24) + ('c' << 16) + ('1' << 8) + '0';
|
||||
/** 16x16@2x "retina" JPEG2000 or PNG icon (10.8+). */
|
||||
int ic11 = ('i' << 24) + ('c' << 16) + ('1' << 8) + '1';
|
||||
/** 32x32@2x "retina" JPEG2000 or PNG icon (10.8+). */
|
||||
int ic12 = ('i' << 24) + ('c' << 16) + ('1' << 8) + '2';
|
||||
/** 128x128@2x "retina" JPEG2000 or PNG icon (10.8+). */
|
||||
int ic13 = ('i' << 24) + ('c' << 16) + ('1' << 8) + '3';
|
||||
/** 256x256@2x "retina" JPEG2000 or PNG icon (10.8+). */
|
||||
int ic14 = ('i' << 24) + ('c' << 16) + ('1' << 8) + '4';
|
||||
|
||||
/** Unknown (Version). */
|
||||
int icnV = ('i' << 24) + ('c' << 16) + ('n' << 8) + 'V';
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2011, Harald Kuhr
|
||||
* Copyright (c) 2017, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -31,6 +31,7 @@
|
||||
package com.twelvemonkeys.imageio.plugins.icns;
|
||||
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||
|
||||
@ -422,7 +423,7 @@ public final class ICNSImageReader extends ImageReaderBase {
|
||||
|
||||
private BufferedImage readForeignFormat(int imageIndex, final ImageReadParam param, final IconResource resource) throws IOException {
|
||||
// TODO: Optimize by caching readers that work?
|
||||
ImageInputStream stream = ImageIO.createImageInputStream(IIOUtil.createStreamAdapter(imageInput, resource.length));
|
||||
ImageInputStream stream = new SubImageInputStream(imageInput, resource.length);
|
||||
|
||||
try {
|
||||
// Try first using ImageIO
|
||||
@ -441,7 +442,7 @@ public final class ICNSImageReader extends ImageReaderBase {
|
||||
}
|
||||
else {
|
||||
stream.close();
|
||||
stream = ImageIO.createImageInputStream(IIOUtil.createStreamAdapter(imageInput, resource.length));
|
||||
stream = new SubImageInputStream(imageInput, resource.length);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
@ -524,10 +525,28 @@ public final class ICNSImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
IconResource resource = IconResource.read(imageInput);
|
||||
// System.err.println("resource: " + resource);
|
||||
|
||||
if (resource.isTOC()) {
|
||||
// TODO: IconResource.readTOC()?
|
||||
int resourceCount = (resource.length - ICNS.RESOURCE_HEADER_SIZE) / ICNS.RESOURCE_HEADER_SIZE;
|
||||
long pos = resource.start + resource.length;
|
||||
|
||||
for (int i = 0; i < resourceCount; i++) {
|
||||
resource = IconResource.read(pos, imageInput);
|
||||
pos += resource.length;
|
||||
addResource(resource);
|
||||
}
|
||||
}
|
||||
else {
|
||||
addResource(resource);
|
||||
}
|
||||
|
||||
lastResourceRead = resource;
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
private void addResource(final IconResource resource) {
|
||||
// Filter out special cases like 'icnV' or 'TOC ' resources
|
||||
if (resource.isMaskType()) {
|
||||
masks.add(resource);
|
||||
@ -535,8 +554,6 @@ public final class ICNSImageReader extends ImageReaderBase {
|
||||
else if (!resource.isUnknownType()) {
|
||||
icons.add(resource);
|
||||
}
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
private void readeFileHeader() throws IOException {
|
||||
|
@ -0,0 +1,273 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 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.imageio.plugins.icns;
|
||||
|
||||
import com.twelvemonkeys.imageio.ImageWriterBase;
|
||||
import com.twelvemonkeys.imageio.stream.SubImageOutputStream;
|
||||
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
||||
|
||||
import javax.imageio.*;
|
||||
import javax.imageio.event.IIOWriteWarningListener;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.spi.ImageWriterSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* ICNSImageWriter
|
||||
*/
|
||||
public final class ICNSImageWriter extends ImageWriterBase {
|
||||
|
||||
private int sequenceIndex = -1;
|
||||
private ImageWriter pngDelegate;
|
||||
|
||||
ICNSImageWriter(ImageWriterSpi provider) {
|
||||
super(provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetMembers() {
|
||||
sequenceIndex = -1;
|
||||
|
||||
if (pngDelegate != null) {
|
||||
pngDelegate.dispose();
|
||||
pngDelegate = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IIOMetadata getDefaultImageMetadata(final ImageTypeSpecifier imageType, final ImageWriteParam param) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IIOMetadata convertImageMetadata(final IIOMetadata inData, final ImageTypeSpecifier imageType, final ImageWriteParam param) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(final IIOMetadata streamMetadata, final IIOImage image, final ImageWriteParam param) throws IOException {
|
||||
prepareWriteSequence(streamMetadata);
|
||||
writeToSequence(image, param);
|
||||
endWriteSequence();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWriteSequence() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareWriteSequence(final IIOMetadata streamMetadata) throws IOException {
|
||||
assertOutput();
|
||||
|
||||
// TODO: Allow TOC resource to be passed as stream metadata?
|
||||
// - We only need number of icons to be written later
|
||||
// - The contents of the TOC could be updated while adding to the sequence
|
||||
|
||||
if (sequenceIndex >= 0) {
|
||||
throw new IllegalStateException("writeSequence already started");
|
||||
}
|
||||
|
||||
writeICNSHeader();
|
||||
sequenceIndex = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endWriteSequence() throws IOException {
|
||||
assertOutput();
|
||||
|
||||
if (sequenceIndex < 0) {
|
||||
throw new IllegalStateException("prepareWriteSequence not called");
|
||||
}
|
||||
|
||||
// TODO: Now that we know the number of icon resources, we could move all data backwards
|
||||
// and write a TOC... But I don't think the benefit will outweigh the cost.
|
||||
|
||||
sequenceIndex = -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToSequence(final IIOImage image, final ImageWriteParam param) throws IOException {
|
||||
assertOutput();
|
||||
|
||||
if (sequenceIndex < 0) {
|
||||
throw new IllegalStateException("prepareWriteSequence not called");
|
||||
}
|
||||
|
||||
if (image.hasRaster()) {
|
||||
throw new UnsupportedOperationException("image has a Raster");
|
||||
}
|
||||
|
||||
long resourceStart = imageOutput.getStreamPosition();
|
||||
|
||||
// TODO: Allow for other formats based on param?
|
||||
// - Uncompressed/RLE (only allowed up to 128x128)?
|
||||
// - JPEG2000 not very likely...
|
||||
|
||||
// Validate icon size, get icon resource type based on size and compression
|
||||
// TODO: Allow smaller, centered in larger square? Resize?
|
||||
imageOutput.writeInt(IconResource.typeFromImage(image.getRenderedImage(), "PNG"));
|
||||
imageOutput.writeInt(0); // Size, update later
|
||||
|
||||
processImageStarted(sequenceIndex);
|
||||
|
||||
// Write icon in PNG format
|
||||
ImageWriter writer = getPNGDelegate();
|
||||
writer.setOutput(new SubImageOutputStream(imageOutput));
|
||||
writer.write(null, image, copyParam(param, writer));
|
||||
|
||||
processImageComplete();
|
||||
|
||||
long resourceEnd = imageOutput.getStreamPosition();
|
||||
if (resourceEnd > Integer.MAX_VALUE) {
|
||||
throw new IIOException("File too large for ICNS");
|
||||
}
|
||||
|
||||
int length = (int) (resourceEnd - resourceStart);
|
||||
|
||||
// Update file length field
|
||||
imageOutput.seek(4);
|
||||
imageOutput.writeInt((int) resourceEnd);
|
||||
|
||||
// Update resource length field
|
||||
imageOutput.seek(resourceStart + 4);
|
||||
imageOutput.writeInt((length));
|
||||
|
||||
// Prepare for next iteration
|
||||
imageOutput.seek(resourceEnd);
|
||||
}
|
||||
|
||||
private ImageWriteParam copyParam(final ImageWriteParam param, ImageWriter writer) {
|
||||
if (param == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ImageWriteParam writeParam = writer.getDefaultWriteParam();
|
||||
writeParam.setSourceSubsampling(param.getSourceXSubsampling(), param.getSourceYSubsampling(), param.getSubsamplingXOffset(), param.getSubsamplingYOffset());
|
||||
writeParam.setSourceRegion(param.getSourceRegion());
|
||||
writeParam.setSourceBands(param.getSourceBands());
|
||||
|
||||
return writeParam;
|
||||
}
|
||||
|
||||
private ImageWriter getPNGDelegate() {
|
||||
if (pngDelegate == null) {
|
||||
// There's always a PNG writer...
|
||||
pngDelegate = ImageIO.getImageWritersByFormatName("PNG").next();
|
||||
pngDelegate.setLocale(getLocale());
|
||||
pngDelegate.addIIOWriteProgressListener(new ProgressListenerBase() {
|
||||
@Override
|
||||
public void imageProgress(ImageWriter source, float percentageDone) {
|
||||
processImageProgress(percentageDone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeAborted(ImageWriter source) {
|
||||
processWriteAborted();
|
||||
}
|
||||
});
|
||||
pngDelegate.addIIOWriteWarningListener(new IIOWriteWarningListener() {
|
||||
@Override
|
||||
public void warningOccurred(ImageWriter source, int imageIndex, String warning) {
|
||||
processWarningOccurred(sequenceIndex, warning);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return pngDelegate;
|
||||
}
|
||||
|
||||
private void writeICNSHeader() throws IOException {
|
||||
if (imageOutput.getStreamPosition() != 0) {
|
||||
throw new IllegalStateException("Stream already written to");
|
||||
}
|
||||
|
||||
imageOutput.writeInt(ICNS.MAGIC);
|
||||
imageOutput.writeInt(8); // Length of file, in bytes, must be updated while writing
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
boolean pngCompression = false;
|
||||
int firstArg = 0;
|
||||
|
||||
while (args.length > firstArg && args[firstArg].charAt(0) == '-') {
|
||||
if (args[firstArg].equals("-p") || args[firstArg].equals("--png")) {
|
||||
pngCompression = true;
|
||||
}
|
||||
|
||||
firstArg++;
|
||||
}
|
||||
|
||||
if (args.length - firstArg < 2) {
|
||||
System.err.println("Usage: command [-p|--png] <output.ico> <input> [<input>...]");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
try (ImageOutputStream out = ImageIO.createImageOutputStream(new File(args[firstArg++]))) {
|
||||
ImageWriter writer = new ICNSImageWriter(null);
|
||||
writer.setOutput(out);
|
||||
|
||||
ImageWriteParam param = writer.getDefaultWriteParam();
|
||||
// For now, we only support PNG...
|
||||
// param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
||||
// param.setCompressionType(pngCompression ? "BI_PNG" : "BI_RGB");
|
||||
|
||||
writer.prepareWriteSequence(null);
|
||||
|
||||
for (int i = firstArg; i < args.length; i++) {
|
||||
File inFile = new File(args[i]);
|
||||
try (ImageInputStream input = ImageIO.createImageInputStream(inFile)) {
|
||||
Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
|
||||
|
||||
if (!readers.hasNext()) {
|
||||
System.err.printf("Can't read %s\n", inFile.getAbsolutePath());
|
||||
}
|
||||
else {
|
||||
ImageReader reader = readers.next();
|
||||
reader.setInput(input);
|
||||
for (int j = 0; j < reader.getNumImages(true); j++) {
|
||||
IIOImage image = reader.readAll(j, null);
|
||||
writer.writeToSequence(image, param);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writer.endWriteSequence();
|
||||
writer.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 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.imageio.plugins.icns;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* ICNSImageWriterSpi
|
||||
*/
|
||||
public final class ICNSImageWriterSpi extends ImageWriterSpiBase {
|
||||
public ICNSImageWriterSpi() {
|
||||
super(new ICNSProviderInfo());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canEncodeImage(final ImageTypeSpecifier type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICNSImageWriter createWriterInstance(final Object extension) {
|
||||
return new ICNSImageWriter(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription(final Locale locale) {
|
||||
return "Apple Icon Image (icns) format Writer";
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Harald Kuhr
|
||||
* Copyright (c) 2017, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -40,7 +40,7 @@ import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
* @version $Id: ICNSProviderInfo.java,v 1.0 20/03/15 harald.kuhr Exp$
|
||||
*/
|
||||
final class ICNSProviderInfo extends ReaderWriterProviderInfo {
|
||||
protected ICNSProviderInfo() {
|
||||
ICNSProviderInfo() {
|
||||
super(
|
||||
ICNSProviderInfo.class,
|
||||
new String[]{"icns", "ICNS"},
|
||||
@ -50,9 +50,12 @@ final class ICNSProviderInfo extends ReaderWriterProviderInfo {
|
||||
},
|
||||
"com.twelvemonkeys.imageio.plugins.icns.ICNSImageReader",
|
||||
new String[] {"com.twelvemonkeys.imageio.plugins.icns.ICNSImageReaderSpi"},
|
||||
"com.twelvemonkeys.imageio.plugins.icns.ICNSImageWriter",
|
||||
new String[] {"com.twelvemonkeys.imageio.plugins.icns.ICNSImageWriterSpi"},
|
||||
false, null, null,
|
||||
null, null,
|
||||
false, null, null, null, null,
|
||||
true, null, null, null, null
|
||||
true, null,
|
||||
null, null, null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2011, Harald Kuhr
|
||||
* Copyright (c) 2017, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -34,6 +34,7 @@ import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.image.RenderedImage;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
@ -46,9 +47,9 @@ import java.io.IOException;
|
||||
final class IconResource {
|
||||
// TODO: Rewrite using subclasses/instances!
|
||||
|
||||
protected final long start;
|
||||
protected final int type;
|
||||
protected final int length;
|
||||
final long start;
|
||||
final int type;
|
||||
final int length;
|
||||
|
||||
private IconResource(long start, int type, int length) {
|
||||
validate(type, length);
|
||||
@ -58,8 +59,12 @@ final class IconResource {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public static IconResource read(ImageInputStream input) throws IOException {
|
||||
return new IconResource(input.getStreamPosition(), input.readInt(), input.readInt());
|
||||
static IconResource read(final ImageInputStream input) throws IOException {
|
||||
return read(input.getStreamPosition(), input);
|
||||
}
|
||||
|
||||
static IconResource read(final long offset, final ImageInputStream input) throws IOException {
|
||||
return new IconResource(offset, input.readInt(), input.readInt());
|
||||
}
|
||||
|
||||
private void validate(int type, int length) {
|
||||
@ -113,9 +118,17 @@ final class IconResource {
|
||||
case ICNS.is32:
|
||||
case ICNS.il32:
|
||||
case ICNS.it32:
|
||||
case ICNS.icp4:
|
||||
case ICNS.icp5:
|
||||
case ICNS.icp6:
|
||||
case ICNS.ic07:
|
||||
case ICNS.ic08:
|
||||
case ICNS.ic09:
|
||||
case ICNS.ic10:
|
||||
case ICNS.ic11:
|
||||
case ICNS.ic12:
|
||||
case ICNS.ic13:
|
||||
case ICNS.ic14:
|
||||
if (length > ICNS.RESOURCE_HEADER_SIZE) {
|
||||
break;
|
||||
}
|
||||
@ -142,7 +155,7 @@ final class IconResource {
|
||||
);
|
||||
}
|
||||
|
||||
public Dimension size() {
|
||||
Dimension size() {
|
||||
switch (type) {
|
||||
case ICNS.ICON:
|
||||
case ICNS.ICN_:
|
||||
@ -156,11 +169,14 @@ final class IconResource {
|
||||
case ICNS.ics8:
|
||||
case ICNS.is32:
|
||||
case ICNS.s8mk:
|
||||
case ICNS.icp4:
|
||||
return new Dimension(16, 16);
|
||||
case ICNS.icl4:
|
||||
case ICNS.icl8:
|
||||
case ICNS.il32:
|
||||
case ICNS.l8mk:
|
||||
case ICNS.icp5:
|
||||
case ICNS.ic11:
|
||||
return new Dimension(32, 32);
|
||||
case ICNS.ich_:
|
||||
case ICNS.ich4:
|
||||
@ -168,12 +184,18 @@ final class IconResource {
|
||||
case ICNS.ih32:
|
||||
case ICNS.h8mk:
|
||||
return new Dimension(48, 48);
|
||||
case ICNS.icp6:
|
||||
case ICNS.ic12:
|
||||
return new Dimension(64, 64);
|
||||
case ICNS.it32:
|
||||
case ICNS.t8mk:
|
||||
case ICNS.ic07:
|
||||
return new Dimension(128, 128);
|
||||
case ICNS.ic08:
|
||||
case ICNS.ic13:
|
||||
return new Dimension(256, 256);
|
||||
case ICNS.ic09:
|
||||
case ICNS.ic14:
|
||||
return new Dimension(512, 512);
|
||||
case ICNS.ic10:
|
||||
return new Dimension(1024, 1024);
|
||||
@ -182,7 +204,7 @@ final class IconResource {
|
||||
}
|
||||
}
|
||||
|
||||
public int depth() {
|
||||
int depth() {
|
||||
switch (type) {
|
||||
case ICNS.ICON:
|
||||
case ICNS.ICN_:
|
||||
@ -208,16 +230,24 @@ final class IconResource {
|
||||
case ICNS.il32:
|
||||
case ICNS.ih32:
|
||||
case ICNS.it32:
|
||||
case ICNS.icp4:
|
||||
case ICNS.icp5:
|
||||
case ICNS.icp6:
|
||||
case ICNS.ic07:
|
||||
case ICNS.ic08:
|
||||
case ICNS.ic09:
|
||||
case ICNS.ic10:
|
||||
case ICNS.ic11:
|
||||
case ICNS.ic12:
|
||||
case ICNS.ic13:
|
||||
case ICNS.ic14:
|
||||
return 32;
|
||||
default:
|
||||
throw new IllegalStateException(String.format("Unknown icon type: '%s'", ICNSUtil.intToStr(type)));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isUnknownType() {
|
||||
boolean isUnknownType() {
|
||||
// Unknown types should simply be skipped when reading
|
||||
switch (type) {
|
||||
case ICNS.ICON:
|
||||
@ -241,16 +271,24 @@ final class IconResource {
|
||||
case ICNS.il32:
|
||||
case ICNS.ih32:
|
||||
case ICNS.it32:
|
||||
case ICNS.icp4:
|
||||
case ICNS.icp5:
|
||||
case ICNS.icp6:
|
||||
case ICNS.ic07:
|
||||
case ICNS.ic08:
|
||||
case ICNS.ic09:
|
||||
case ICNS.ic10:
|
||||
case ICNS.ic11:
|
||||
case ICNS.ic12:
|
||||
case ICNS.ic13:
|
||||
case ICNS.ic14:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean hasMask() {
|
||||
boolean hasMask() {
|
||||
switch (type) {
|
||||
case ICNS.ICN_:
|
||||
case ICNS.icm_:
|
||||
@ -262,7 +300,7 @@ final class IconResource {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isMaskType() {
|
||||
boolean isMaskType() {
|
||||
switch (type) {
|
||||
case ICNS.s8mk:
|
||||
case ICNS.l8mk:
|
||||
@ -274,7 +312,7 @@ final class IconResource {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isCompressed() {
|
||||
boolean isCompressed() {
|
||||
switch (type) {
|
||||
case ICNS.is32:
|
||||
case ICNS.il32:
|
||||
@ -291,18 +329,30 @@ final class IconResource {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isForeignFormat() {
|
||||
boolean isForeignFormat() {
|
||||
// Recent entries contains full JPEG 2000 or PNG streams
|
||||
switch (type) {
|
||||
case ICNS.icp4:
|
||||
case ICNS.icp5:
|
||||
case ICNS.icp6:
|
||||
case ICNS.ic07:
|
||||
case ICNS.ic08:
|
||||
case ICNS.ic09:
|
||||
case ICNS.ic10:
|
||||
case ICNS.ic11:
|
||||
case ICNS.ic12:
|
||||
case ICNS.ic13:
|
||||
case ICNS.ic14:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isTOC() {
|
||||
return type == ICNS.TOC_;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (int) start ^ type;
|
||||
@ -322,4 +372,62 @@ final class IconResource {
|
||||
public String toString() {
|
||||
return String.format("%s['%s' start: %d, length: %d%s]", getClass().getSimpleName(), ICNSUtil.intToStr(type), start, length, isCompressed() ? " (compressed)" : "");
|
||||
}
|
||||
|
||||
static int typeFromImage(final RenderedImage image, final String compression) {
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
|
||||
if (width == height) {
|
||||
switch (compression) {
|
||||
case "JPEG2000":
|
||||
case "PNG":
|
||||
return typeFromWidthForeign(width);
|
||||
case "None":
|
||||
case "RLE":
|
||||
return typeFromWidthNative(width);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported compression for ICNS: " + compression);
|
||||
}
|
||||
}
|
||||
|
||||
// Note: Strictly, the format supports an ancient 16x12 size, but I doubt we'll ever support that
|
||||
throw new IllegalArgumentException(String.format("Unsupported dimensions for ICNS, only square icons supported: %dx%d", width, height));
|
||||
}
|
||||
// NOTE: These also needs a mask, if there's an alpha channel
|
||||
|
||||
private static int typeFromWidthNative(final int width) {
|
||||
switch (width) {
|
||||
case 16:
|
||||
return ICNS.is32;
|
||||
case 32:
|
||||
return ICNS.il32;
|
||||
case 48:
|
||||
return ICNS.ih32;
|
||||
case 128:
|
||||
return ICNS.it32;
|
||||
default:
|
||||
throw new IllegalArgumentException(String.format("Unsupported dimensions for ICNS, only 16, 32, 48 and 128 supported: %dx%d", width, width));
|
||||
}
|
||||
}
|
||||
|
||||
private static int typeFromWidthForeign(final int width) {
|
||||
switch (width) {
|
||||
case 16:
|
||||
return ICNS.icp4;
|
||||
case 32:
|
||||
return ICNS.icp5;
|
||||
case 64:
|
||||
return ICNS.icp6;
|
||||
case 128:
|
||||
return ICNS.ic07;
|
||||
case 256:
|
||||
return ICNS.ic08;
|
||||
case 512:
|
||||
return ICNS.ic09;
|
||||
case 1024:
|
||||
return ICNS.ic10;
|
||||
default:
|
||||
throw new IllegalArgumentException(String.format("Unsupported dimensions for ICNS, only multiples of 2 from 16 to 1024 supported: %dx%d", width, width));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
com.twelvemonkeys.imageio.plugins.icns.ICNSImageWriterSpi
|
@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 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.imageio.plugins.icns;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.IIOImage;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.RenderedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* ICNSImageWriterTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: ICNSImageWriterTest.java,v 1.0 25/08/2018 harald.kuhr Exp$
|
||||
*/
|
||||
public class ICNSImageWriterTest extends ImageWriterAbstractTest {
|
||||
|
||||
private final ICNSImageWriterSpi provider = new ICNSImageWriterSpi();
|
||||
|
||||
@Override
|
||||
protected ImageWriter createImageWriter() {
|
||||
return provider.createWriterInstance(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<? extends RenderedImage> getTestData() {
|
||||
return asList(
|
||||
new BufferedImage(32, 32, BufferedImage.TYPE_INT_ARGB),
|
||||
new BufferedImage(32, 32, BufferedImage.TYPE_BYTE_BINARY),
|
||||
new BufferedImage(32, 32, BufferedImage.TYPE_BYTE_INDEXED),
|
||||
new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB),
|
||||
// new BufferedImage(48, 48, BufferedImage.TYPE_INT_ARGB), // Only supported for compression None/RLE
|
||||
new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB),
|
||||
new BufferedImage(128, 128, BufferedImage.TYPE_INT_ARGB),
|
||||
new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB),
|
||||
new BufferedImage(512, 512, BufferedImage.TYPE_INT_ARGB),
|
||||
new BufferedImage(1024, 1024, BufferedImage.TYPE_INT_ARGB)
|
||||
);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testWriteNonSquare() throws IOException {
|
||||
// ICNS only supports square icons (except some arcane 16x12 we don't currently support)
|
||||
ImageWriter writer = createImageWriter();
|
||||
try (ImageOutputStream stream = ImageIO.createImageOutputStream(new ByteArrayOutputStream())) {
|
||||
|
||||
writer.setOutput(stream);
|
||||
|
||||
writer.write(new BufferedImage(32, 64, BufferedImage.TYPE_INT_ARGB));
|
||||
|
||||
}
|
||||
finally {
|
||||
writer.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testWriteBadSize() throws IOException {
|
||||
// ICNS only supports sizes in multiples of 2 (16, 32, 64, ..., 1024 + 48 and 96)
|
||||
ImageWriter writer = createImageWriter();
|
||||
try (ImageOutputStream stream = ImageIO.createImageOutputStream(new ByteArrayOutputStream())) {
|
||||
|
||||
writer.setOutput(stream);
|
||||
|
||||
writer.write(new BufferedImage(17, 17, BufferedImage.TYPE_INT_ARGB));
|
||||
|
||||
}
|
||||
finally {
|
||||
writer.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSequencesSupported() {
|
||||
ImageWriter writer = createImageWriter();
|
||||
try {
|
||||
assertTrue(writer.canWriteSequence());
|
||||
}
|
||||
finally {
|
||||
writer.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testWriteSequenceNotStarted() throws IOException {
|
||||
// ICNS only supports sizes in multiples of 2 (16, 32, 64, ..., 1024 + 48 and 96)
|
||||
ImageWriter writer = createImageWriter();
|
||||
try (ImageOutputStream stream = ImageIO.createImageOutputStream(new ByteArrayOutputStream())) {
|
||||
|
||||
writer.setOutput(stream);
|
||||
|
||||
BufferedImage image = new BufferedImage(32, 32, BufferedImage.TYPE_INT_ARGB);
|
||||
writer.writeToSequence(new IIOImage(image, null, null), writer.getDefaultWriteParam());
|
||||
|
||||
}
|
||||
finally {
|
||||
writer.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testEndSequenceNotStarted() throws IOException {
|
||||
// ICNS only supports sizes in multiples of 2 (16, 32, 64, ..., 1024 + 48 and 96)
|
||||
ImageWriter writer = createImageWriter();
|
||||
try (ImageOutputStream stream = ImageIO.createImageOutputStream(new ByteArrayOutputStream())) {
|
||||
|
||||
writer.setOutput(stream);
|
||||
writer.endWriteSequence();
|
||||
}
|
||||
finally {
|
||||
writer.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testPrepareSequenceAlreadyStarted() throws IOException {
|
||||
// ICNS only supports sizes in multiples of 2 (16, 32, 64, ..., 1024 + 48 and 96)
|
||||
ImageWriter writer = createImageWriter();
|
||||
try (ImageOutputStream stream = ImageIO.createImageOutputStream(new ByteArrayOutputStream())) {
|
||||
|
||||
writer.setOutput(stream);
|
||||
writer.prepareWriteSequence(null);
|
||||
writer.prepareWriteSequence(null);
|
||||
}
|
||||
finally {
|
||||
writer.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteSequence() throws IOException {
|
||||
ImageWriter writer = createImageWriter();
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
|
||||
try (ImageOutputStream stream = ImageIO.createImageOutputStream(output)) {
|
||||
writer.setOutput(stream);
|
||||
|
||||
writer.prepareWriteSequence(null);
|
||||
for (RenderedImage image : getTestData()) {
|
||||
IIOImage iioImage = new IIOImage(image, null, null);
|
||||
writer.writeToSequence(iioImage, writer.getDefaultWriteParam());
|
||||
}
|
||||
writer.endWriteSequence();
|
||||
}
|
||||
finally {
|
||||
writer.dispose();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user