    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.imaging.formats.ico;
    018
    019import static org.apache.commons.imaging.common.BinaryFunctions.read2Bytes;
    020import static org.apache.commons.imaging.common.BinaryFunctions.read4Bytes;
    021import static org.apache.commons.imaging.common.BinaryFunctions.readByte;
    022import static org.apache.commons.imaging.common.BinaryFunctions.readBytes;
    023
    024import java.awt.Dimension;
    025import java.awt.image.BufferedImage;
    026import java.io.ByteArrayInputStream;
    027import java.io.ByteArrayOutputStream;
    028import java.io.IOException;
    029import java.io.InputStream;
    030import java.io.OutputStream;
    031import java.io.PrintWriter;
    032import java.nio.ByteOrder;
    033import java.util.ArrayList;
    034import java.util.List;
    035
    036import org.apache.commons.imaging.ImageFormat;
    037import org.apache.commons.imaging.ImageFormats;
    038import org.apache.commons.imaging.ImageInfo;
    039import org.apache.commons.imaging.ImageParser;
    040import org.apache.commons.imaging.ImageReadException;
    041import org.apache.commons.imaging.ImageWriteException;
    042import org.apache.commons.imaging.Imaging;
    043import org.apache.commons.imaging.PixelDensity;
    044import org.apache.commons.imaging.common.BinaryOutputStream;
    045import org.apache.commons.imaging.common.ImageMetadata;
    046import org.apache.commons.imaging.common.bytesource.ByteSource;
    047import org.apache.commons.imaging.formats.bmp.BmpImageParser;
    048import org.apache.commons.imaging.palette.PaletteFactory;
    049import org.apache.commons.imaging.palette.SimplePalette;
    050
    051public class IcoImageParser extends ImageParser<IcoImagingParameters> {
    052    private static final String DEFAULT_EXTENSION = ImageFormats.ICO.getDefaultExtension();
    053    private static final String[] ACCEPTED_EXTENSIONS = ImageFormats.ICO.getExtensions();
    054
    055    public IcoImageParser() {
    056        super.setByteOrder(ByteOrder.LITTLE_ENDIAN);
    057    }
    058
    059    @Override
    060    public IcoImagingParameters getDefaultParameters() {
    061        return new IcoImagingParameters();
    062    }
    063
    064    @Override
    065    public String getName() {
    066        return "ico-Custom";
    067    }
    068
    069    @Override
    070    public String getDefaultExtension() {
    071        return DEFAULT_EXTENSION;
    072    }
    073
    074    @Override
    075    protected String[] getAcceptedExtensions() {
    076        return ACCEPTED_EXTENSIONS;
    077    }
    078
    079    @Override
    080    protected ImageFormat[] getAcceptedTypes() {
    081        return new ImageFormat[] { ImageFormats.ICO, //
    082        };
    083    }
    084
    085    // TODO should throw UOE
    086    @Override
    087    public ImageMetadata getMetadata(final ByteSource byteSource, final IcoImagingParameters params)
    088            throws ImageReadException, IOException {
    089        return null;
    090    }
    091
    092    // TODO should throw UOE
    093    @Override
    094    public ImageInfo getImageInfo(final ByteSource byteSource, final IcoImagingParameters params)
    095            throws ImageReadException, IOException {
    096        return null;
    097    }
    098
    099    // TODO should throw UOE
    100    @Override
    101    public Dimension getImageSize(final ByteSource byteSource, final IcoImagingParameters params)
    102            throws ImageReadException, IOException {
    103        return null;
    104    }
    105
    106    // TODO should throw UOE
    107    @Override
    108    public byte[] getICCProfileBytes(final ByteSource byteSource, final IcoImagingParameters params)
    109            throws ImageReadException, IOException {
    110        return null;
    111    }
    112
    113    private static class FileHeader {
    114        public final int reserved; // Reserved (2 bytes), always 0
    115        public final int iconType; // IconType (2 bytes), if the image is an
    116                                   // icon it?s 1, for cursors the value is 2.
    117        public final int iconCount; // IconCount (2 bytes), number of icons in
    118                                    // this file.
    119
    120        FileHeader(final int reserved, final int iconType, final int iconCount) {
    121            this.reserved = reserved;
    122            this.iconType = iconType;
    123            this.iconCount = iconCount;
    124        }
    125
    126        public void dump(final PrintWriter pw) {
    127            pw.println("FileHeader");
    128            pw.println("Reserved: " + reserved);
    129            pw.println("IconType: " + iconType);
    130            pw.println("IconCount: " + iconCount);
    131            pw.println();
    132        }
    133    }
    134
    135    private FileHeader readFileHeader(final InputStream is) throws ImageReadException, IOException {
    136        final int reserved = read2Bytes("Reserved", is, "Not a Valid ICO File", getByteOrder());
    137        final int iconType = read2Bytes("IconType", is, "Not a Valid ICO File", getByteOrder());
    138        final int iconCount = read2Bytes("IconCount", is, "Not a Valid ICO File", getByteOrder());
    139
    140        if (reserved != 0) {
    141            throw new ImageReadException("Not a Valid ICO File: reserved is " + reserved);
    142        }
    143        if (iconType != 1 && iconType != 2) {
    144            throw new ImageReadException("Not a Valid ICO File: icon type is " + iconType);
    145        }
    146
    147        return new FileHeader(reserved, iconType, iconCount);
    148
    149    }
    150
    151    private static class IconInfo {
    152        public final byte width;
    153        public final byte height;
    154        public final byte colorCount;
    155        public final byte reserved;
    156        public final int planes;
    157        public final int bitCount;
    158        public final int imageSize;
    159        public final int imageOffset;
    160
    161        IconInfo(final byte width, final byte height,
    162                final byte colorCount, final byte reserved, final int planes,
    163                final int bitCount, final int imageSize, final int imageOffset) {
    164            this.width = width;
    165            this.height = height;
    166            this.colorCount = colorCount;
    167            this.reserved = reserved;
    168            this.planes = planes;
    169            this.bitCount = bitCount;
    170            this.imageSize = imageSize;
    171            this.imageOffset = imageOffset;
    172        }
    173
    174        public void dump(final PrintWriter pw) {
    175            pw.println("IconInfo");
    176            pw.println("Width: " + width);
    177            pw.println("Height: " + height);
    178            pw.println("ColorCount: " + colorCount);
    179            pw.println("Reserved: " + reserved);
    180            pw.println("Planes: " + planes);
    181            pw.println("BitCount: " + bitCount);
    182            pw.println("ImageSize: " + imageSize);
    183            pw.println("ImageOffset: " + imageOffset);
    184        }
    185    }
    186
    187    private IconInfo readIconInfo(final InputStream is) throws IOException {
    188        // Width (1 byte), Width of Icon (1 to 255)
    189        final byte width = readByte("Width", is, "Not a Valid ICO File");
    190        // Height (1 byte), Height of Icon (1 to 255)
    191        final byte height = readByte("Height", is, "Not a Valid ICO File");
    192        // ColorCount (1 byte), Number of colors, either
    193        // 0 for 24 bit or higher,
    194        // 2 for monochrome or 16 for 16 color images.
    195        final byte colorCount = readByte("ColorCount", is, "Not a Valid ICO File");
    196        // Reserved (1 byte), Not used (always 0)
    197        final byte reserved = readByte("Reserved", is, "Not a Valid ICO File");
    198        // Planes (2 bytes), always 1
    199        final int planes = read2Bytes("Planes", is, "Not a Valid ICO File", getByteOrder());
    200        // BitCount (2 bytes), number of bits per pixel (1 for monochrome,
    201        // 4 for 16 colors, 8 for 256 colors, 24 for true colors,
    202        // 32 for true colors + alpha channel)
    203        final int bitCount = read2Bytes("BitCount", is, "Not a Valid ICO File", getByteOrder());
    204        // ImageSize (4 bytes), Length of resource in bytes
    205        final int imageSize = read4Bytes("ImageSize", is, "Not a Valid ICO File", getByteOrder());
    206        // ImageOffset (4 bytes), start of the image in the file
    207        final int imageOffset = read4Bytes("ImageOffset", is, "Not a Valid ICO File", getByteOrder());
    208
    209        return new IconInfo(width, height, colorCount, reserved, planes, bitCount, imageSize, imageOffset);
    210    }
    211
    212    private static class BitmapHeader {
    213        public final int size;
    214        public final int width;
    215        public final int height;
    216        public final int planes;
    217        public final int bitCount;
    218        public final int compression;
    219        public final int sizeImage;
    220        public final int xPelsPerMeter;
    221        public final int yPelsPerMeter;
    222        public final int colorsUsed;
    223        public final int colorsImportant;
    224
    225        BitmapHeader(final int size, final int width, final int height,
    226                final int planes, final int bitCount, final int compression,
    227                final int sizeImage, final int pelsPerMeter,
    228                final int pelsPerMeter2, final int colorsUsed,
    229                final int colorsImportant) {
    230            this.size = size;
    231            this.width = width;
    232            this.height = height;
    233            this.planes = planes;
    234            this.bitCount = bitCount;
    235            this.compression = compression;
    236            this.sizeImage = sizeImage;
    237            xPelsPerMeter = pelsPerMeter;
    238            yPelsPerMeter = pelsPerMeter2;
    239            this.colorsUsed = colorsUsed;
    240            this.colorsImportant = colorsImportant;
    241        }
    242
    243        public void dump(final PrintWriter pw) {
    244            pw.println("BitmapHeader");
    245
    246            pw.println("Size: " + size);
    247            pw.println("Width: " + width);
    248            pw.println("Height: " + height);
    249            pw.println("Planes: " + planes);
    250            pw.println("BitCount: " + bitCount);
    251            pw.println("Compression: " + compression);
    252            pw.println("SizeImage: " + sizeImage);
    253            pw.println("XPelsPerMeter: " + xPelsPerMeter);
    254            pw.println("YPelsPerMeter: " + yPelsPerMeter);
    255            pw.println("ColorsUsed: " + colorsUsed);
    256            pw.println("ColorsImportant: " + colorsImportant);
    257        }
    258    }
    259
    260    private abstract static class IconData {
    261        public final IconInfo iconInfo;
    262
    263        IconData(final IconInfo iconInfo) {
    264            this.iconInfo = iconInfo;
    265        }
    266
    267        public void dump(final PrintWriter pw) {
    268            iconInfo.dump(pw);
    269            pw.println();
    270            dumpSubclass(pw);
    271        }
    272
    273        protected abstract void dumpSubclass(PrintWriter pw);
    274
    275        public abstract BufferedImage readBufferedImage()
    276                throws ImageReadException;
    277    }
    278
    279    private static class BitmapIconData extends IconData {
    280        public final BitmapHeader header;
    281        public final BufferedImage bufferedImage;
    282
    283        BitmapIconData(final IconInfo iconInfo,
    284                final BitmapHeader header, final BufferedImage bufferedImage) {
    285            super(iconInfo);
    286            this.header = header;
    287            this.bufferedImage = bufferedImage;
    288        }
    289
    290        @Override
    291        public BufferedImage readBufferedImage() throws ImageReadException {
    292            return bufferedImage;
    293        }
    294
    295        @Override
    296        protected void dumpSubclass(final PrintWriter pw) {
    297            pw.println("BitmapIconData");
    298            header.dump(pw);
    299            pw.println();
    300        }
    301    }
    302
    303    private static class PNGIconData extends IconData {
    304        public final BufferedImage bufferedImage;
    305
    306        PNGIconData(final IconInfo iconInfo,
    307                final BufferedImage bufferedImage) {
    308            super(iconInfo);
    309            this.bufferedImage = bufferedImage;
    310        }
    311
    312        @Override
    313        public BufferedImage readBufferedImage() {
    314            return bufferedImage;
    315        }
    316
    317        @Override
    318        protected void dumpSubclass(final PrintWriter pw) {
    319            pw.println("PNGIconData");
    320            pw.println();
    321        }
    322    }
    323
    324    private IconData readBitmapIconData(final byte[] iconData, final IconInfo fIconInfo)
    325            throws ImageReadException, IOException {
    326        final ByteArrayInputStream is = new ByteArrayInputStream(iconData);
    327        final int size = read4Bytes("size", is, "Not a Valid ICO File", getByteOrder()); // Size (4
    328                                                                   // bytes),
    329                                                                   // size of
    330                                                                   // this
    331                                                                   // structure
    332                                                                   // (always
    333                                                                   // 40)
    334        final int width = read4Bytes("width", is, "Not a Valid ICO File", getByteOrder()); // Width (4
    335                                                                     // bytes),
    336                                                                     // width of
    337                                                                     // the
    338                                                                     // image
    339                                                                     // (same as
    340                                                                     // iconinfo.width)
    341        final int height = read4Bytes("height", is, "Not a Valid ICO File", getByteOrder()); // Height
    342                                                                       // (4
    343                                                                       // bytes),
    344                                                                       // scanlines
    345                                                                       // in the
    346                                                                       // color
    347                                                                       // map +
    348                                                                       // transparent
    349                                                                       // map
    350                                                                       // (iconinfo.height
    351                                                                       // * 2)
    352        final int planes = read2Bytes("planes", is, "Not a Valid ICO File", getByteOrder()); // Planes
    353                                                                       // (2
    354                                                                       // bytes),
    355                                                                       // always
    356                                                                       // 1
    357        final int bitCount = read2Bytes("bitCount", is, "Not a Valid ICO File", getByteOrder()); // BitCount
    358                                                                           // (2
    359                                                                           // bytes),
    360                                                                           // 1,4,8,16,24,32
    361                                                                           // (see
    362                                                                           // iconinfo
    363                                                                           // for
    364                                                                           // details)
    365        int compression = read4Bytes("compression", is, "Not a Valid ICO File", getByteOrder()); // Compression
    366                                                                                 // (4
    367                                                                                 // bytes),
    368                                                                                 // we
    369                                                                                 // don?t
    370                                                                                 // use
    371                                                                                 // this
    372                                                                                 // (0)
    373        final int sizeImage = read4Bytes("sizeImage", is, "Not a Valid ICO File", getByteOrder()); // SizeImage
    374                                                                             // (4
    375                                                                             // bytes),
    376                                                                             // we
    377                                                                             // don?t
    378                                                                             // use
    379                                                                             // this
    380                                                                             // (0)
    381        final int xPelsPerMeter = read4Bytes("xPelsPerMeter", is,
    382                "Not a Valid ICO File", getByteOrder()); // XPelsPerMeter (4 bytes), we don?t
    383                                         // use this (0)
    384        final int yPelsPerMeter = read4Bytes("yPelsPerMeter", is,
    385                "Not a Valid ICO File", getByteOrder()); // YPelsPerMeter (4 bytes), we don?t
    386                                         // use this (0)
    387        final int colorsUsed = read4Bytes("colorsUsed", is, "Not a Valid ICO File", getByteOrder()); // ColorsUsed
    388                                                                               // (4
    389                                                                               // bytes),
    390                                                                               // we
    391                                                                               // don?t
    392                                                                               // use
    393                                                                               // this
    394                                                                               // (0)
    395        final int colorsImportant = read4Bytes("ColorsImportant", is,
    396                "Not a Valid ICO File", getByteOrder()); // ColorsImportant (4 bytes), we don?t
    397                                         // use this (0)
    398        int redMask = 0;
    399        int greenMask = 0;
    400        int blueMask = 0;
    401        int alphaMask = 0;
    402        if (compression == 3) {
    403            redMask = read4Bytes("redMask", is, "Not a Valid ICO File", getByteOrder());
    404            greenMask = read4Bytes("greenMask", is, "Not a Valid ICO File", getByteOrder());
    405            blueMask = read4Bytes("blueMask", is, "Not a Valid ICO File", getByteOrder());
    406        }
    407        final byte[] restOfFile = readBytes("RestOfFile", is, is.available());
    408
    409        if (size != 40) {
    410            throw new ImageReadException("Not a Valid ICO File: Wrong bitmap header size " + size);
    411        }
    412        if (planes != 1) {
    413            throw new ImageReadException("Not a Valid ICO File: Planes can't be " + planes);
    414        }
    415
    416        if (compression == 0 && bitCount == 32) {
    417            // 32 BPP RGB icons need an alpha channel, but BMP files don't have
    418            // one unless BI_BITFIELDS is used...
    419            compression = 3;
    420            redMask = 0x00ff0000;
    421            greenMask = 0x0000ff00;
    422            blueMask = 0x000000ff;
    423            alphaMask = 0xff000000;
    424        }
    425
    426        final BitmapHeader header = new BitmapHeader(size, width, height, planes,
    427                bitCount, compression, sizeImage, xPelsPerMeter, yPelsPerMeter,
    428                colorsUsed, colorsImportant);
    429
    430        final int bitmapPixelsOffset = 14 + 56 + 4 * ((colorsUsed == 0 && bitCount <= 8) ? (1 << bitCount)
    431                : colorsUsed);
    432        final int bitmapSize = 14 + 56 + restOfFile.length;
    433
    434        final ByteArrayOutputStream baos = new ByteArrayOutputStream(bitmapSize);
    435        try (BinaryOutputStream bos = new BinaryOutputStream(baos, ByteOrder.LITTLE_ENDIAN)) {
    436            bos.write('B');
    437            bos.write('M');
    438            bos.write4Bytes(bitmapSize);
    439            bos.write4Bytes(0);
    440            bos.write4Bytes(bitmapPixelsOffset);
    441
    442            bos.write4Bytes(56);
    443            bos.write4Bytes(width);
    444            bos.write4Bytes(height / 2);
    445            bos.write2Bytes(planes);
    446            bos.write2Bytes(bitCount);
    447            bos.write4Bytes(compression);
    448            bos.write4Bytes(sizeImage);
    449            bos.write4Bytes(xPelsPerMeter);
    450            bos.write4Bytes(yPelsPerMeter);
    451            bos.write4Bytes(colorsUsed);
    452            bos.write4Bytes(colorsImportant);
    453            bos.write4Bytes(redMask);
    454            bos.write4Bytes(greenMask);
    455            bos.write4Bytes(blueMask);
    456            bos.write4Bytes(alphaMask);
    457            bos.write(restOfFile);
    458            bos.flush();
    459        }
    460
    461        final ByteArrayInputStream bmpInputStream = new ByteArrayInputStream(baos.toByteArray());
    462        final BufferedImage bmpImage = new BmpImageParser().getBufferedImage(bmpInputStream, null);
    463
    464        // Transparency map is optional with 32 BPP icons, because they already
    465        // have
    466        // an alpha channel, and Windows only uses the transparency map when it
    467        // has to
    468        // display the icon on a < 32 BPP screen. But it's still used instead of
    469        // alpha
    470        // if the image would be completely transparent with alpha...
    471        int t_scanline_size = (width + 7) / 8;
    472        if ((t_scanline_size % 4) != 0) {
    473            t_scanline_size += 4 - (t_scanline_size % 4); // pad scanline to 4
    474                                                          // byte size.
    475        }
    476        final int colorMapSizeBytes = t_scanline_size * (height / 2);
    477        byte[] transparencyMap = null;
    478        try {
    479            transparencyMap = readBytes("transparency_map",
    480                    bmpInputStream, colorMapSizeBytes,
    481                    "Not a Valid ICO File");
    482        } catch (final IOException ioEx) {
    483            if (bitCount != 32) {
    484                throw ioEx;
    485            }
    486        }
    487
    488        boolean allAlphasZero = true;
    489        if (bitCount == 32) {
    490            for (int y = 0; allAlphasZero && y < bmpImage.getHeight(); y++) {
    491                for (int x = 0; x < bmpImage.getWidth(); x++) {
    492                    if ((bmpImage.getRGB(x, y) & 0xff000000) != 0) {
    493                        allAlphasZero = false;
    494                        break;
    495                    }
    496                }
    497            }
    498        }
    499        BufferedImage resultImage;
    500        if (allAlphasZero) {
    501            resultImage = new BufferedImage(bmpImage.getWidth(),
    502                    bmpImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
    503            for (int y = 0; y < resultImage.getHeight(); y++) {
    504                for (int x = 0; x < resultImage.getWidth(); x++) {
    505                    int alpha = 0xff;
    506                    if (transparencyMap != null) {
    507                        final int alphaByte = 0xff & transparencyMap[t_scanline_size
    508                                * (bmpImage.getHeight() - y - 1) + (x / 8)];
    509                        alpha = 0x01 & (alphaByte >> (7 - (x % 8)));
    510                        alpha = (alpha == 0) ? 0xff : 0x00;
    511                    }
    512                    resultImage.setRGB(x, y, (alpha << 24)
    513                            | (0xffffff & bmpImage.getRGB(x, y)));
    514                }
    515            }
    516        } else {
    517            resultImage = bmpImage;
    518        }
    519        return new BitmapIconData(fIconInfo, header, resultImage);
    520    }
    521
    522    private IconData readIconData(final byte[] iconData, final IconInfo fIconInfo)
    523            throws ImageReadException, IOException {
    524        final ImageFormat imageFormat = Imaging.guessFormat(iconData);
    525        if (imageFormat.equals(ImageFormats.PNG)) {
    526            final BufferedImage bufferedImage = Imaging.getBufferedImage(iconData);
    527            return new PNGIconData(fIconInfo, bufferedImage);
    528        }
    529        return readBitmapIconData(iconData, fIconInfo);
    530    }
    531
    532    private static class ImageContents {
    533        public final FileHeader fileHeader;
    534        public final IconData[] iconDatas;
    535
    536        ImageContents(final FileHeader fileHeader, final IconData[] iconDatas) {
    537            this.fileHeader = fileHeader;
    538            this.iconDatas = iconDatas;
    539        }
    540    }
    541
    542    private ImageContents readImage(final ByteSource byteSource)
    543            throws ImageReadException, IOException {
    544        try (InputStream is = byteSource.getInputStream()) {
    545            final FileHeader fileHeader = readFileHeader(is);
    546
    547            final IconInfo[] fIconInfos = new IconInfo[fileHeader.iconCount];
    548            for (int i = 0; i < fileHeader.iconCount; i++) {
    549                fIconInfos[i] = readIconInfo(is);
    550            }
    551
    552            final IconData[] fIconDatas = new IconData[fileHeader.iconCount];
    553            for (int i = 0; i < fileHeader.iconCount; i++) {
    554                final byte[] iconData = byteSource.getBlock(
    555                        fIconInfos[i].imageOffset, fIconInfos[i].imageSize);
    556                fIconDatas[i] = readIconData(iconData, fIconInfos[i]);
    557            }
    558
    559            return new ImageContents(fileHeader, fIconDatas);
    560        }
    561    }
    562
    563    @Override
    564    public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource)
    565            throws ImageReadException, IOException {
    566        final ImageContents contents = readImage(byteSource);
    567        contents.fileHeader.dump(pw);
    568        for (final IconData iconData : contents.iconDatas) {
    569            iconData.dump(pw);
    570        }
    571        return true;
    572    }
    573
    574    @Override
    575    public final BufferedImage getBufferedImage(final ByteSource byteSource,
    576            final IcoImagingParameters params) throws ImageReadException, IOException {
    577        final ImageContents contents = readImage(byteSource);
    578        final FileHeader fileHeader = contents.fileHeader;
    579        if (fileHeader.iconCount > 0) {
    580            return contents.iconDatas[0].readBufferedImage();
    581        }
    582        throw new ImageReadException("No icons in ICO file");
    583    }
    584
    585    @Override
    586    public List<BufferedImage> getAllBufferedImages(final ByteSource byteSource)
    587            throws ImageReadException, IOException {
    588        final ImageContents contents = readImage(byteSource);
    589
    590        final FileHeader fileHeader = contents.fileHeader;
    591        final List<BufferedImage> result = new ArrayList<>(fileHeader.iconCount);
    592        for (int i = 0; i < fileHeader.iconCount; i++) {
    593            final IconData iconData = contents.iconDatas[i];
    594
    595            final BufferedImage image = iconData.readBufferedImage();
    596
    597            result.add(image);
    598        }
    599
    600        return result;
    601    }
    602
    603    // public boolean extractImages(ByteSource byteSource, File dst_dir,
    604    // String dst_root, ImageParser encoder) throws ImageReadException,
    605    // IOException, ImageWriteException
    606    // {
    607    // ImageContents contents = readImage(byteSource);
    608    //
    609    // FileHeader fileHeader = contents.fileHeader;
    610    // for (int i = 0; i < fileHeader.iconCount; i++)
    611    // {
    612    // IconData iconData = contents.iconDatas[i];
    613    //
    614    // BufferedImage image = readBufferedImage(iconData);
    615    //
    616    // int size = Math.max(iconData.iconInfo.Width,
    617    // iconData.iconInfo.Height);
    618    // File file = new File(dst_dir, dst_root + "_" + size + "_"
    619    // + iconData.iconInfo.BitCount
    620    // + encoder.getDefaultExtension());
    621    // encoder.writeImage(image, new FileOutputStream(file), null);
    622    // }
    623    //
    624    // return true;
    625    // }
    626
    627    @Override
    628    public void writeImage(final BufferedImage src, final OutputStream os, IcoImagingParameters params) throws ImageWriteException, IOException {
    629        if (params == null) {
    630            params = new IcoImagingParameters();
    631        }
    632        final PixelDensity pixelDensity = params.getPixelDensity();
    633
    634        final PaletteFactory paletteFactory = new PaletteFactory();
    635        final SimplePalette palette = paletteFactory.makeExactRgbPaletteSimple(src, 256);
    636        final int bitCount;
    637        // If we can't obtain an exact rgb palette, we set the bit count to either 24 or 32
    638        // so there is a relation between having a palette and the bit count.
    639        if (palette == null) {
    640            final boolean hasTransparency = paletteFactory.hasTransparency(src);
    641            if (hasTransparency) {
    642                bitCount = 32;
    643            } else {
    644                bitCount = 24;
    645            }
    646        } else if (palette.length() <= 2) {
    647            bitCount = 1;
    648        } else if (palette.length() <= 16) {
    649            bitCount = 4;
    650        } else {
    651            bitCount = 8;
    652        }
    653
    654        final BinaryOutputStream bos = new BinaryOutputStream(os, ByteOrder.LITTLE_ENDIAN);
    655
    656        int scanline_size = (bitCount * src.getWidth() + 7) / 8;
    657        if ((scanline_size % 4) != 0) {
    658            scanline_size += 4 - (scanline_size % 4); // pad scanline to 4 byte
    659                                                      // size.
    660        }
    661        int t_scanline_size = (src.getWidth() + 7) / 8;
    662        if ((t_scanline_size % 4) != 0) {
    663            t_scanline_size += 4 - (t_scanline_size % 4); // pad scanline to 4
    664                                                          // byte size.
    665        }
    666        final int imageSize = 40 + 4 * (bitCount <= 8 ? (1 << bitCount) : 0)
    667                + src.getHeight() * scanline_size + src.getHeight()
    668                * t_scanline_size;
    669
    670        // ICONDIR
    671        bos.write2Bytes(0); // reserved
    672        bos.write2Bytes(1); // 1=ICO, 2=CUR
    673        bos.write2Bytes(1); // count
    674
    675        // ICONDIRENTRY
    676        int iconDirEntryWidth = src.getWidth();
    677        int iconDirEntryHeight = src.getHeight();
    678        if (iconDirEntryWidth > 255 || iconDirEntryHeight > 255) {
    679            iconDirEntryWidth = 0;
    680            iconDirEntryHeight = 0;
    681        }
    682        bos.write(iconDirEntryWidth);
    683        bos.write(iconDirEntryHeight);
    684        bos.write((bitCount >= 8) ? 0 : (1 << bitCount));
    685        bos.write(0); // reserved
    686        bos.write2Bytes(1); // color planes
    687        bos.write2Bytes(bitCount);
    688        bos.write4Bytes(imageSize);
    689        bos.write4Bytes(22); // image offset
    690
    691        // BITMAPINFOHEADER
    692        bos.write4Bytes(40); // size
    693        bos.write4Bytes(src.getWidth());
    694        bos.write4Bytes(2 * src.getHeight());
    695        bos.write2Bytes(1); // planes
    696        bos.write2Bytes(bitCount);
    697        bos.write4Bytes(0); // compression
    698        bos.write4Bytes(0); // image size
    699        bos.write4Bytes(pixelDensity == null ? 0 : (int) Math.round(pixelDensity.horizontalDensityMetres())); // x pixels per meter
    700        bos.write4Bytes(pixelDensity == null ? 0 : (int) Math.round(pixelDensity.horizontalDensityMetres())); // y pixels per meter
    701        bos.write4Bytes(0); // colors used, 0 = (1 << bitCount) (ignored)
    702        bos.write4Bytes(0); // colors important
    703
    704        if (palette != null) {
    705            for (int i = 0; i < (1 << bitCount); i++) {
    706                if (i < palette.length()) {
    707                    final int argb = palette.getEntry(i);
    708                    bos.write3Bytes(argb);
    709                    bos.write(0);
    710                } else {
    711                    bos.write4Bytes(0);
    712                }
    713            }
    714        }
    715
    716        int bitCache = 0;
    717        int bitsInCache = 0;
    718        final int rowPadding = scanline_size - (bitCount * src.getWidth() + 7) / 8;
    719        for (int y = src.getHeight() - 1; y >= 0; y--) {
    720            for (int x = 0; x < src.getWidth(); x++) {
    721                final int argb = src.getRGB(x, y);
    722                // Remember there is a relation between having a rgb palette and the bit count, see above comment
    723                if (palette == null) {
    724                    if (bitCount == 24) {
    725                        bos.write3Bytes(argb);
    726                    } else if (bitCount == 32) {
    727                        bos.write4Bytes(argb);
    728                    }
    729                } else {
    730                    if (bitCount < 8) {
    731                        final int rgb = 0xffffff & argb;
    732                        final int index = palette.getPaletteIndex(rgb);
    733                        bitCache <<= bitCount;
    734                        bitCache |= index;
    735                        bitsInCache += bitCount;
    736                        if (bitsInCache >= 8) {
    737                            bos.write(0xff & bitCache);
    738                            bitCache = 0;
    739                            bitsInCache = 0;
    740                        }
    741                    } else if (bitCount == 8) {
    742                        final int rgb = 0xffffff & argb;
    743                        final int index = palette.getPaletteIndex(rgb);
    744                        bos.write(0xff & index);
    745                    }
    746                }
    747            }
    748
    749            if (bitsInCache > 0) {
    750                bitCache <<= (8 - bitsInCache);
    751                bos.write(0xff & bitCache);
    752                bitCache = 0;
    753                bitsInCache = 0;
    754            }
    755
    756            for (int x = 0; x < rowPadding; x++) {
    757                bos.write(0);
    758            }
    759        }
    760
    761        final int t_row_padding = t_scanline_size - (src.getWidth() + 7) / 8;
    762        for (int y = src.getHeight() - 1; y >= 0; y--) {
    763            for (int x = 0; x < src.getWidth(); x++) {
    764                final int argb = src.getRGB(x, y);
    765                final int alpha = 0xff & (argb >> 24);
    766                bitCache <<= 1;
    767                if (alpha == 0) {
    768                    bitCache |= 1;
    769                }
    770                bitsInCache++;
    771                if (bitsInCache >= 8) {
    772                    bos.write(0xff & bitCache);
    773                    bitCache = 0;
    774                    bitsInCache = 0;
    775                }
    776            }
    777
    778            if (bitsInCache > 0) {
    779                bitCache <<= (8 - bitsInCache);
    780                bos.write(0xff & bitCache);
    781                bitCache = 0;
    782                bitsInCache = 0;
    783            }
    784
    785            for (int x = 0; x < t_row_padding; x++) {
    786                bos.write(0);
    787            }
    788        }
    789        bos.close();
    790    }
    791}