changeset 1273:a83c72313001

Added parser to read ico images
author Jiri Vanek <jvanek@redhat.com>
date Thu, 12 Nov 2015 18:03:47 +0100
parents 104317f48096
children 67a3d4c59e19
files ChangeLog netx/net/sourceforge/jnlp/controlpanel/desktopintegrationeditor/JListUtils.java netx/net/sourceforge/jnlp/tools/ico/impl/IcoException.java netx/net/sourceforge/jnlp/tools/ico/impl/IcoHeader.java netx/net/sourceforge/jnlp/tools/ico/impl/IcoHeaderEntry.java netx/net/sourceforge/jnlp/tools/ico/impl/ImageInputStreamIco.java
diffstat 6 files changed, 475 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Thu Nov 12 17:19:20 2015 +0100
+++ b/ChangeLog	Thu Nov 12 18:03:47 2015 +0100
@@ -1,3 +1,18 @@
+2015-11-12  Jiri Vanek  <jvanek@redhat.com>
+
+	Added parser to read ico images
+	* netx/net/sourceforge/jnlp/controlpanel/desktopintegrationeditor/JListUtils.java:
+	When reading images, also ico is attempted to parse.
+	* netx/net/sourceforge/jnlp/tools/ico/impl/IcoException.java: new class,
+	exception for special cases in ico parsing	
+	* netx/net/sourceforge/jnlp/tools/ico/impl/IcoHeader.java: new class, parser
+	and holder of parsed information of header of ico file
+	* netx/net/sourceforge/jnlp/tools/ico/impl/IcoHeaderEntry.java: new class,
+	parser and holder of parsed information of headers of individual images stored
+	in header of ico file
+	* netx/net/sourceforge/jnlp/tools/ico/impl/ImageInputStreamIco.java: parser
+	of icon files from ImageInputStream
+
 2015-11-12  Jiri Vanek  <jvanek@redhat.com>
 
 	Added desktop integration dialog
--- a/netx/net/sourceforge/jnlp/controlpanel/desktopintegrationeditor/JListUtils.java	Thu Nov 12 17:19:20 2015 +0100
+++ b/netx/net/sourceforge/jnlp/controlpanel/desktopintegrationeditor/JListUtils.java	Thu Nov 12 18:03:47 2015 +0100
@@ -40,11 +40,13 @@
 import java.awt.Image;
 import java.awt.image.BufferedImage;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FilenameFilter;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.regex.Pattern;
 import javax.imageio.ImageIO;
+import javax.imageio.stream.ImageInputStream;
 import javax.swing.DefaultListCellRenderer;
 import javax.swing.Icon;
 import javax.swing.ImageIcon;
@@ -54,6 +56,7 @@
 import javax.swing.ListModel;
 import javax.swing.event.ListDataListener;
 import net.sourceforge.jnlp.config.InfrastructureFileDescriptor;
+import net.sourceforge.jnlp.tools.ico.impl.ImageInputStreamIco;
 import net.sourceforge.jnlp.util.XDesktopEntry;
 
 public class JListUtils {
@@ -357,7 +360,17 @@
      */
     private static ImageIcon createImageIcon(File f, String description) {
         try {
-            BufferedImage i = ImageIO.read(f);
+            BufferedImage i;
+            try(ImageInputStream is = ImageIO.createImageInputStream(new FileInputStream(f))) {
+                ImageInputStreamIco ico = new ImageInputStreamIco(is);
+                i = ico.getImage(0);
+            } catch (Exception eex) {
+                //not ico
+                i = null;
+            }
+            if (i == null) {
+                i = ImageIO.read(f);
+            }
             return new ImageIcon(i.getScaledInstance(50, 50, Image.SCALE_SMOOTH));
         } catch (Exception ex) {
             //not worthy to log it. No image is there and so be it.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/netx/net/sourceforge/jnlp/tools/ico/impl/IcoException.java	Thu Nov 12 18:03:47 2015 +0100
@@ -0,0 +1,46 @@
+/*
+ Copyright (C) 2015 Red Hat, Inc.
+
+ This file is part of IcedTea.
+
+ IcedTea is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ IcedTea is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with IcedTea; see the file COPYING.  If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301 USA.
+
+ Linking this library statically or dynamically with other modules is
+ making a combined work based on this library.  Thus, the terms and
+ conditions of the GNU General Public License cover the whole
+ combination.
+
+ As a special exception, the copyright holders of this library give you
+ permission to link this library with independent modules to produce an
+ executable, regardless of the license terms of these independent
+ modules, and to copy and distribute the resulting executable under
+ terms of your choice, provided that you also meet, for each linked
+ independent module, the terms and conditions of the license of that
+ module.  An independent module is a module which is not derived from
+ or based on this library.  If you modify this library, you may extend
+ this exception to your version of the library, but you are not
+ obligated to do so.  If you do not wish to do so, delete this
+ exception statement from your version. */
+package net.sourceforge.jnlp.tools.ico.impl;
+
+
+class IcoException extends Exception {
+
+    public IcoException(String string) {
+        super(string);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/netx/net/sourceforge/jnlp/tools/ico/impl/IcoHeader.java	Thu Nov 12 18:03:47 2015 +0100
@@ -0,0 +1,87 @@
+/*
+ Copyright (C) 2015 Red Hat, Inc.
+
+ This file is part of IcedTea.
+
+ IcedTea is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ IcedTea is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with IcedTea; see the file COPYING.  If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301 USA.
+
+ Linking this library statically or dynamically with other modules is
+ making a combined work based on this library.  Thus, the terms and
+ conditions of the GNU General Public License cover the whole
+ combination.
+
+ As a special exception, the copyright holders of this library give you
+ permission to link this library with independent modules to produce an
+ executable, regardless of the license terms of these independent
+ modules, and to copy and distribute the resulting executable under
+ terms of your choice, provided that you also meet, for each linked
+ independent module, the terms and conditions of the license of that
+ module.  An independent module is a module which is not derived from
+ or based on this library.  If you modify this library, you may extend
+ this exception to your version of the library, but you are not
+ obligated to do so.  If you do not wish to do so, delete this
+ exception statement from your version. */
+package net.sourceforge.jnlp.tools.ico.impl;
+
+import java.io.IOException;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+import javax.imageio.stream.ImageInputStream;
+
+/**
+ * http://www.daubnet.com/en/file-format-ico
+ */
+public class IcoHeader {
+
+    private final int reserved; //0
+    private final int type; //should be 1 (0 is for cusrsor?)
+    final int countOfIcons;
+    final List<IcoHeaderEntry> entries; //size 16*countOfIcons bytes
+    private final ByteOrder originalOrder;
+
+    public IcoHeader(ImageInputStream src) throws IOException, IcoException {
+        originalOrder = src.getByteOrder();
+        try {
+            src.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+            reserved = src.readUnsignedShort();
+            type = src.readUnsignedShort();
+            isIco();
+            countOfIcons = src.readUnsignedShort();
+            entries = new ArrayList<>(countOfIcons);
+            for (int x = 0; x < countOfIcons; x++) {
+                entries.add(new IcoHeaderEntry(src));
+            }
+        } finally {
+            src.setByteOrder(originalOrder);
+        }
+    }
+
+    private void isIco() throws IcoException {
+        if (reserved != 0 || (type != 1)) {
+            throw new IcoException("Invalid header. Expected 0 and 1, got " + reserved + " and " + type);
+        }
+    }
+
+    public List<IcoHeaderEntry> getEntries() {
+        return entries;
+    }
+
+    public int getCountOfIcons() {
+        return countOfIcons;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/netx/net/sourceforge/jnlp/tools/ico/impl/IcoHeaderEntry.java	Thu Nov 12 18:03:47 2015 +0100
@@ -0,0 +1,143 @@
+package net.sourceforge.jnlp.tools.ico.impl;
+
+/*
+ Copyright (C) 2015 Red Hat, Inc.
+
+ This file is part of IcedTea.
+
+ IcedTea is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ IcedTea is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with IcedTea; see the file COPYING.  If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301 USA.
+
+ Linking this library statically or dynamically with other modules is
+ making a combined work based on this library.  Thus, the terms and
+ conditions of the GNU General Public License cover the whole
+ combination.
+
+ As a special exception, the copyright holders of this library give you
+ permission to link this library with independent modules to produce an
+ executable, regardless of the license terms of these independent
+ modules, and to copy and distribute the resulting executable under
+ terms of your choice, provided that you also meet, for each linked
+ independent module, the terms and conditions of the license of that
+ module.  An independent module is a module which is not derived from
+ or based on this library.  If you modify this library, you may extend
+ this exception to your version of the library, but you are not
+ obligated to do so.  If you do not wish to do so, delete this
+ exception statement from your version. */
+import java.io.IOException;
+import javax.imageio.stream.ImageInputStream;
+
+public class IcoHeaderEntry {
+
+    private int width;
+    private int height;
+    private int colorCount;
+    private final int reserved;
+    private final int planes; //should be 1 but  I met quite a lot of  0
+    private final int bitCount;
+    private final int sizeInBytes; // InfoHeader + ANDbitmap + XORbitmap
+    private final int fileOffset; //FilePos, where InfoHeader starts
+
+    public IcoHeaderEntry(ImageInputStream src) throws IOException, IcoException {
+        width = src.read();
+        height = src.read();
+        colorCount = src.read();
+        // sentence "Number of Colors (2,16, 0=256) " is form doubnet
+        //unluckily, both  0==0 and 0==256 does exists
+        //going with doubnet by default
+        if (colorCount == 0) {
+            colorCount = 256;
+        }
+        reserved = src.read();
+        planes = src.readUnsignedShort();
+        isIcoHeader();
+        bitCount = src.readUnsignedShort();
+        sizeInBytes = src.readInt();
+        fileOffset = src.readInt();
+    }
+
+    private IcoHeaderEntry(int width, int height, int colorCount, int planes, int bitCount, int sizeInBytes, int fileOffset) {
+        this.width = width;
+        this.height = height;
+        this.colorCount = colorCount;
+        this.reserved = 0;
+        this.planes = planes;
+        this.bitCount = bitCount;
+        this.sizeInBytes = sizeInBytes;
+        this.fileOffset = fileOffset;
+    }
+
+    private IcoHeaderEntry provideMonochromeHeader() {
+        //each bit in byte stores 8 pixels values
+        return new IcoHeaderEntry(width, height, 1, planes, 1, width * height / 8, fileOffset + sizeInBytes);
+    }
+
+    private void isIcoHeader() throws IcoException {
+        if (reserved != 0 || (planes != 1 && planes != 0)) {
+            throw new IcoException("Invalid header. Expected 0 and 1(0?), got " + reserved + " and " + planes);
+        }
+    }
+
+    /**
+     * @return the colorCount
+     */
+    int getColorCount() {
+        return colorCount;
+    }
+
+    /**
+     * @param colorCount the colorCount to set
+     */
+    void setColorCount(int colorCount) {
+        this.colorCount = colorCount;
+    }
+
+    /**
+     * @return the width
+     */
+    public int getWidth() {
+        return width;
+    }
+
+    /**
+     * @param width the width to set
+     */
+    void setWidth(int width) {
+        this.width = width;
+    }
+
+    /**
+     * @return the height
+     */
+    public int getHeight() {
+        return height;
+    }
+
+    /**
+     * @param height the height to set
+     */
+    void setHeight(int height) {
+        this.height = height;
+    }
+
+    int getSizeInBytes() {
+        return sizeInBytes;
+    }
+
+    int getFileOffset() {
+        return fileOffset;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/netx/net/sourceforge/jnlp/tools/ico/impl/ImageInputStreamIco.java	Thu Nov 12 18:03:47 2015 +0100
@@ -0,0 +1,170 @@
+/*
+ Copyright (C) 2015 Red Hat, Inc.
+
+ This file is part of IcedTea.
+
+ IcedTea is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ IcedTea is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with IcedTea; see the file COPYING.  If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301 USA.
+
+ Linking this library statically or dynamically with other modules is
+ making a combined work based on this library.  Thus, the terms and
+ conditions of the GNU General Public License cover the whole
+ combination.
+
+ As a special exception, the copyright holders of this library give you
+ permission to link this library with independent modules to produce an
+ executable, regardless of the license terms of these independent
+ modules, and to copy and distribute the resulting executable under
+ terms of your choice, provided that you also meet, for each linked
+ independent module, the terms and conditions of the license of that
+ module.  An independent module is a module which is not derived from
+ or based on this library.  If you modify this library, you may extend
+ this exception to your version of the library, but you are not
+ obligated to do so.  If you do not wish to do so, delete this
+ exception statement from your version. */
+package net.sourceforge.jnlp.tools.ico.impl;
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import javax.imageio.ImageIO;
+import javax.imageio.stream.ImageInputStream;
+
+public class ImageInputStreamIco {
+
+    private final IcoHeader header;
+    private final List<BufferedImage> images; //size 16*countOfIcons bytes
+
+    public IcoHeader getHeader() {
+        return header;
+    }
+
+    public BufferedImage getImage(int i) {
+        return images.get(i);
+    }
+
+    public ImageInputStreamIco(ImageInputStream src) throws IOException, IcoException {
+        this.header = new IcoHeader(src);
+        images = new ArrayList<>(header.countOfIcons);
+        for (IcoHeaderEntry e : header.entries) {
+            BufferedImage image = readImage(e, src);
+            images.add(image);
+        }
+    }
+
+    private static void readMask(IcoHeaderEntry e, ImageInputStream src1) throws IOException {
+        //acording to spec, behind img bytes, should be another bytes, with AND bitmap.Hoewer, I had not found them.
+        //however, that means, that transaprency is lost... But bit offsets mathces.. so...
+        //IcoHeader.IcoHeaderEntry q = e.provideMonochromeHeader();
+        //src1.getStreamPosition();
+        //byte[] mask = new byte[q.sizeInBytes];
+        //src1.readFully(mask);
+    }
+
+    private static BufferedImage readImage(IcoHeaderEntry e, ImageInputStream src1) throws IOException {
+        BufferedImage image;
+        byte[] img = new byte[e.getSizeInBytes()];
+        if (src1.getStreamPosition() != e.getFileOffset()) {
+            //I had never seen this thrown, Still, is it worthy to tempt it, or rather read and die later?
+            //throw new IOException("Stream position do nto match expected position. Bmp(or png) will read wrongly");
+        }
+        src1.readFully(img);
+        try {
+            image = parse(img, e);
+            //readMask(e, src1);
+        } catch (EOFException ex) {
+            //some icons do not honour that 0 is 256. Retrying
+            if (e.getColorCount() != 0) {
+                e.setColorCount(0);
+                image = parse(img, e);
+                //readMask(e, src1);
+            } else {
+                throw ex;
+            }
+        }
+        return image;
+    }
+
+    private static BufferedImage parse(byte[] img, IcoHeaderEntry e) throws IOException {
+        ByteArrayInputStream bis = new ByteArrayInputStream(img);
+        BufferedImage image = null;
+        try {
+            image = ImageIO.read(bis);
+        } catch (Exception ex) {
+            //not png
+        }
+        if (image != null) {
+            fixSizesInHeader(e, image);
+            return image;
+        }
+        //bmp
+        img = prefixByFakeHeader(img, e);
+        bis = new ByteArrayInputStream(img);
+        //dont try catch this one. you will break it
+        image = ImageIO.read(bis);
+
+        return image;
+    }
+
+    private static void fixSizesInHeader(IcoHeaderEntry e, BufferedImage image) {
+        //may happen for png
+        if (e.getWidth() == 0) {
+            e.setWidth(image.getWidth());
+        }
+        if (e.getHeight() == 0) {
+            e.setHeight(image.getHeight());
+        }
+    }
+
+    private static byte[] prefixByFakeHeader(final byte[] origArray, IcoHeaderEntry e) {
+        int fakingArray = 14;
+        byte[] img = new byte[fakingArray + e.getSizeInBytes()];
+        for (int i = 0; i < origArray.length; i++) {
+            byte p = origArray[i];
+            img[i + 14] = p;
+
+        }
+        //fake header
+        //http://www.daubnet.com/en/file-format-bmp
+        int size = e.getSizeInBytes() + fakingArray;
+        img[0] = 'B';
+        img[1] = 'M';
+        img[2] = (byte) (size & 0xFF);
+        img[3] = (byte) ((size >> 8) & 0xFF);
+        img[4] = (byte) ((size >> 16) & 0xFF);
+        img[5] = (byte) ((size >> 24) & 0xFF);
+        img[6] = 0;
+        img[7] = 0;
+        img[8] = 0;
+        img[9] = 0;
+        int ofset = fakingArray + 40 + 4 * e.getColorCount();
+        img[10] = (byte) (ofset & 0xFF);
+        img[11] = (byte) ((ofset >> 8) & 0xFF);
+        img[12] = (byte) ((ofset >> 16) & 0xFF);
+        img[13] = (byte) ((ofset >> 24) & 0xFF);
+        //ico is storing height as height of XOR + height of AND bitmaps
+        //that is 2 x hight. Bitmap expects only height of single image
+        int tmpHeight = e.getHeight();
+        img[fakingArray + 4/*size*/ + 4/*width*/] = (byte) (tmpHeight & 0xFF);
+        img[fakingArray + 4/*size*/ + 4/*width*/ + 1] = (byte) ((tmpHeight >> 8) & 0xFF);
+        img[fakingArray + 4/*size*/ + 4/*width*/ + 2] = (byte) ((tmpHeight >> 16) & 0xFF);
+        img[fakingArray + 4/*size*/ + 4/*width*/ + 3] = (byte) ((tmpHeight >> 24) & 0xFF);
+        return img;
+    }
+
+}