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