diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS.java index 89af08f5..3879a65f 100644 --- a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS.java +++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNS.java @@ -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'; diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReader.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReader.java index a4476dc5..82962c98 100644 --- a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReader.java +++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReader.java @@ -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 { diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageWriter.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageWriter.java new file mode 100644 index 00000000..56641a40 --- /dev/null +++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageWriter.java @@ -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] [...]"); + 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 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(); + } + } + +} diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageWriterSpi.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageWriterSpi.java new file mode 100644 index 00000000..bf207e39 --- /dev/null +++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageWriterSpi.java @@ -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"; + } +} diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSProviderInfo.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSProviderInfo.java index 153257ef..9fe2b6a6 100644 --- a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSProviderInfo.java +++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSProviderInfo.java @@ -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 ); } } diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/IconResource.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/IconResource.java index bde54b32..d9b111a2 100644 --- a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/IconResource.java +++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/IconResource.java @@ -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)); + } + } } diff --git a/imageio/imageio-icns/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi b/imageio/imageio-icns/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi index c5bd0d3a..80a16d1d 100644 --- a/imageio/imageio-icns/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi +++ b/imageio/imageio-icns/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi @@ -1 +1 @@ -com.twelvemonkeys.imageio.plugins.icns.ICNSImageReaderSpi \ No newline at end of file +com.twelvemonkeys.imageio.plugins.icns.ICNSImageReaderSpi diff --git a/imageio/imageio-icns/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi b/imageio/imageio-icns/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi new file mode 100644 index 00000000..44f82bc5 --- /dev/null +++ b/imageio/imageio-icns/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi @@ -0,0 +1 @@ +com.twelvemonkeys.imageio.plugins.icns.ICNSImageWriterSpi diff --git a/imageio/imageio-icns/src/test/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageWriterTest.java b/imageio/imageio-icns/src/test/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageWriterTest.java new file mode 100644 index 00000000..d1af9df7 --- /dev/null +++ b/imageio/imageio-icns/src/test/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageWriterTest.java @@ -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 Harald Kuhr + * @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 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(); + } + } +} \ No newline at end of file