2
Copyright (C) 2012 Volker Berlin (i-net software)
4
This software is provided 'as-is', without any express or implied
5
warranty. In no event will the authors be held liable for any damages
6
arising from the use of this software.
8
Permission is granted to anyone to use this software for any purpose,
9
including commercial applications, and to alter it and redistribute it
10
freely, subject to the following restrictions:
12
1. The origin of this software must not be misrepresented; you must not
13
claim that you wrote the original software. If you use this software
14
in a product, an acknowledgment in the product documentation would be
15
appreciated but is not required.
16
2. Altered source versions must be plainly marked as such, and must not be
17
misrepresented as being the original software.
18
3. This notice may not be removed or altered from any source distribution.
33
/// Encodes images in ICO format.
35
internal class IconFactory
39
/// Indicates that ICO data represents an icon (.ICO).
41
private const int TYPE_ICON = 1;
43
private BinaryWriter writer;
46
/// Creates a new instance of IconFactory </summary>
47
internal IconFactory()
52
/// Create a Icon from the given list of images
54
/// <param name="images">list of images</param>
55
/// <returns></returns>
56
internal Icon CreateIcon(java.util.List images, Size size)
58
MemoryStream stream = new MemoryStream();
59
Write(images, stream);
61
return new Icon(stream, size);
65
/// Encodes and writes multiple images without colour depth conversion.
67
/// <param name="images">
68
/// the list of source images to be encoded </param>
69
/// <param name="stream">
70
/// the output to which the encoded image will be written </param>
71
internal void Write(java.util.List images, System.IO.Stream stream)
73
writer = new BinaryWriter(stream);
75
int count = images.size();
78
WriteFileHeader(count, TYPE_ICON);
80
// file offset where images start
81
int fileOffset = 6 + count * 16;
83
// icon entries 16 * count
84
for (int i = 0; i < count; i++)
86
BufferedImage imgc = (BufferedImage)images.get(i);
87
fileOffset += WriteIconEntry(imgc, fileOffset);
91
for (int i = 0; i < count; i++)
93
BufferedImage imgc = (BufferedImage)images.get(i);
96
WriteInfoHeader(imgc);
98
if (imgc.getColorModel().getPixelSize() <= 8)
100
IndexColorModel icm = (IndexColorModel)imgc.getColorModel();
104
WriteXorBitmap(imgc);
106
WriteAndBitmap(imgc);
112
/// Writes the ICO file header for an ICO containing the given number of
115
/// <param name="count">
116
/// the number of images in the ICO </param>
117
/// <param name="type">
119
private void WriteFileHeader(int count, int type)
122
writer.Write((short)0);
124
writer.Write((short)type);
126
writer.Write((short)count);
130
/// Encodes the <em>AND</em> bitmap for the given image according the its
131
/// alpha channel (transparency) and writes it to the given output.
133
/// <param name="img">
134
/// the image to encode as the <em>AND</em> bitmap. </param>
135
private void WriteAndBitmap(BufferedImage img)
137
WritableRaster alpha = img.getAlphaRaster();
139
// indexed transparency (eg. GIF files)
140
if (img.getColorModel() is IndexColorModel && img.getColorModel().hasAlpha())
142
int w = img.getWidth();
143
int h = img.getHeight();
145
int bytesPerLine = GetBytesPerLine1(w);
147
byte[] line = new byte[bytesPerLine];
149
IndexColorModel icm = (IndexColorModel)img.getColorModel();
150
Raster raster = img.getRaster();
152
for (int y = h - 1; y >= 0; y--)
154
for (int x = 0; x < w; x++)
158
// int a = alpha.getSample(x, y, 0);
159
int p = raster.getSample(x, y, 0);
160
int a = icm.getAlpha(p);
161
// invert bit since and mask is applied to xor mask
163
line[bi] = SetBit(line[bi], i, b);
170
else if (alpha == null)
172
int h = img.getHeight();
173
int w = img.getWidth();
174
// calculate number of bytes per line, including 32-bit padding
175
int bytesPerLine = GetBytesPerLine1(w);
177
byte[] line = new byte[bytesPerLine];
178
for (int i = 0; i < bytesPerLine; i++)
183
for (int y = h - 1; y >= 0; y--)
188
// transparency (ARGB, etc. eg. PNG)
191
int w = img.getWidth();
192
int h = img.getHeight();
194
int bytesPerLine = GetBytesPerLine1(w);
196
byte[] line = new byte[bytesPerLine];
198
for (int y = h - 1; y >= 0; y--)
200
for (int x = 0; x < w; x++)
204
int a = alpha.getSample(x, y, 0);
205
// invert bit since and mask is applied to xor mask
207
line[bi] = SetBit(line[bi], i, b);
215
private void WriteXorBitmap(BufferedImage img)
217
Raster raster = img.getRaster();
218
switch (img.getColorModel().getPixelSize())
233
Raster alpha = img.getAlphaRaster();
234
Write32(raster, alpha);
240
/// Writes the <tt>InfoHeader</tt> structure to output
243
private void WriteInfoHeader(BufferedImage img)
245
// Size of InfoHeader structure = 40
248
writer.Write(img.getWidth());
250
writer.Write(img.getHeight() * 2);
252
writer.Write((short)1);
254
writer.Write((short)img.getColorModel().getPixelSize());
257
// Image size - compressed size of image or 0 if Compression = 0
259
// horizontal resolution pixels/meter
261
// vertical resolution pixels/meter
263
// Colors used - number of colors actually used
265
// Colors important - number of important colors 0 = all
270
/// Writes the <tt>IconEntry</tt> structure to output
272
private int WriteIconEntry(BufferedImage img, int fileOffset)
274
// Width 1 byte Cursor Width (16, 32 or 64)
275
int width = img.getWidth();
276
writer.Write((byte)(width == 256 ? 0 : width));
277
// Height 1 byte Cursor Height (16, 32 or 64 , most commonly = Width)
278
int height = img.getHeight();
279
writer.Write((byte)(height == 256 ? 0 : height));
280
// ColorCount 1 byte Number of Colors (2,16, 0=256)
281
short BitCount = (short)img.getColorModel().getPixelSize();
282
int NumColors = 1 << (BitCount == 32 ? 24 : (int)BitCount);
283
byte ColorCount = (byte)(NumColors >= 256 ? 0 : NumColors);
284
writer.Write((byte)ColorCount);
285
// Reserved 1 byte =0
286
writer.Write((byte)0);
288
writer.Write((short)1);
289
// BitCount 2 byte bits per pixel (1, 4, 8)
290
writer.Write((short)BitCount);
291
// SizeInBytes 4 byte Size of (InfoHeader + ANDbitmap + XORbitmap)
292
int cmapSize = GetColorMapSize(BitCount);
293
int xorSize = GetBitmapSize(width, height, BitCount);
294
int andSize = GetBitmapSize(width, height, 1);
295
int size = 40 + cmapSize + xorSize + andSize;
297
// FileOffset 4 byte FilePos, where InfoHeader starts
298
writer.Write(fileOffset);
303
/// Writes the colour map resulting from the source <tt>IndexColorModel</tt>.
305
/// <param name="icm">
306
/// the source <tt>IndexColorModel</tt> </param>
307
private void WriteColorMap(IndexColorModel icm)
309
int mapSize = icm.getMapSize();
310
for (int i = 0; i < mapSize; i++)
312
int rgb = icm.getRGB(i);
313
byte r = (byte)(rgb >> 16);
314
byte g = (byte)(rgb >> 8);
315
byte b = (byte)(rgb);
319
writer.Write((byte)0);
324
/// Calculates the number of bytes per line required for the given width in
325
/// pixels, for a 1-bit bitmap. Lines are always padded to the next 4-byte
328
/// <param name="width">
329
/// the width in pixels </param>
330
/// <returns> the number of bytes per line </returns>
331
private int GetBytesPerLine1(int width)
333
int ret = (int)width / 8;
336
ret = (ret / 4 + 1) * 4;
342
/// Calculates the number of bytes per line required for the given with in
343
/// pixels, for a 4-bit bitmap. Lines are always padded to the next 4-byte
346
/// <param name="width">
347
/// the width in pixels </param>
348
/// <returns> the number of bytes per line </returns>
349
private int GetBytesPerLine4(int width)
351
int ret = (int)width / 2;
354
ret = (ret / 4 + 1) * 4;
360
/// Calculates the number of bytes per line required for the given with in
361
/// pixels, for a 8-bit bitmap. Lines are always padded to the next 4-byte
364
/// <param name="width">
365
/// the width in pixels </param>
366
/// <returns> the number of bytes per line </returns>
367
private int GetBytesPerLine8(int width)
372
ret = (ret / 4 + 1) * 4;
378
/// Calculates the number of bytes per line required for the given with in
379
/// pixels, for a 24-bit bitmap. Lines are always padded to the next 4-byte
382
/// <param name="width">
383
/// the width in pixels </param>
384
/// <returns> the number of bytes per line </returns>
385
private int GetBytesPerLine24(int width)
390
ret = (ret / 4 + 1) * 4;
396
/// Calculates the size in bytes of a bitmap with the specified size and
400
/// the width in pixels </param>
402
/// the height in pixels </param>
403
/// <param name="bpp">
404
/// the colour depth (bits per pixel) </param>
405
/// <returns> the size of the bitmap in bytes </returns>
406
private int GetBitmapSize(int w, int h, int bpp)
408
int bytesPerLine = 0;
412
bytesPerLine = GetBytesPerLine1(w);
415
bytesPerLine = GetBytesPerLine4(w);
418
bytesPerLine = GetBytesPerLine8(w);
421
bytesPerLine = GetBytesPerLine24(w);
424
bytesPerLine = w * 4;
427
int ret = bytesPerLine * h;
432
/// Encodes and writes raster data as a 1-bit bitmap.
434
/// <param name="raster">
435
/// the source raster data </param>
436
private void Write1(Raster raster)
438
int bytesPerLine = GetBytesPerLine1(raster.getWidth());
440
byte[] line = new byte[bytesPerLine];
442
for (int y = raster.getHeight() - 1; y >= 0; y--)
444
for (int i = 0; i < bytesPerLine; i++)
449
for (int x = 0; x < raster.getWidth(); x++)
453
int index = raster.getSample(x, y, 0);
454
line[bi] = SetBit(line[bi], i, index);
462
/// Encodes and writes raster data as a 4-bit bitmap.
464
/// <param name="raster">
465
/// the source raster data </param>
466
private void Write4(Raster raster)
468
int width = raster.getWidth();
469
int height = raster.getHeight();
471
// calculate bytes per line
472
int bytesPerLine = GetBytesPerLine4(width);
475
byte[] line = new byte[bytesPerLine];
477
// encode and write lines
478
for (int y = height - 1; y >= 0; y--)
482
for (int i = 0; i < bytesPerLine; i++)
487
// encode raster data for line
488
for (int x = 0; x < width; x++)
491
// calculate buffer index
494
// calculate nibble index (high order or low order)
498
int index = raster.getSample(x, y, 0);
499
// set color index in buffer
500
line[bi] = SetNibble(line[bi], i, index);
503
// write line data (padding bytes included)
509
/// Encodes and writes raster data as an 8-bit bitmap.
511
/// <param name="raster">
512
/// the source raster data </param>
513
private void Write8(Raster raster)
515
int width = raster.getWidth();
516
int height = raster.getHeight();
518
// calculate bytes per line
519
int bytesPerLine = GetBytesPerLine8(width);
522
for (int y = height - 1; y >= 0; y--)
525
// write raster data for each line
526
for (int x = 0; x < width; x++)
529
// get color index for pixel
530
byte index = (byte)raster.getSample(x, y, 0);
536
// write padding bytes at end of line
537
for (int i = width; i < bytesPerLine; i++)
539
writer.Write((byte)0);
546
/// Encodes and writes raster data as a 24-bit bitmap.
548
/// <param name="raster">
549
/// the source raster data </param>
550
private void Write24(Raster raster)
552
int width = raster.getWidth();
553
int height = raster.getHeight();
555
// calculate bytes per line
556
int bytesPerLine = GetBytesPerLine24(width);
559
for (int y = height - 1; y >= 0; y--)
562
// write pixel data for each line
563
for (int x = 0; x < width; x++)
566
// get RGB values for pixel
567
byte r = (byte)raster.getSample(x, y, 0);
568
byte g = (byte)raster.getSample(x, y, 1);
569
byte b = (byte)raster.getSample(x, y, 2);
577
// write padding bytes at end of line
578
for (int i = width * 3; i < bytesPerLine; i++)
580
writer.Write((byte)0);
586
/// Encodes and writes raster data, together with alpha (transparency) data,
587
/// as a 32-bit bitmap.
589
/// <param name="raster">
590
/// the source raster data </param>
591
/// <param name="alpha">
592
/// the source alpha data </param>
593
private void Write32(Raster raster, Raster alpha)
595
int width = raster.getWidth();
596
int height = raster.getHeight();
599
for (int y = height - 1; y >= 0; y--)
602
// write pixel data for each line
603
for (int x = 0; x < width; x++)
607
byte r = (byte)raster.getSample(x, y, 0);
608
byte g = (byte)raster.getSample(x, y, 1);
609
byte b = (byte)raster.getSample(x, y, 2);
610
byte a = (byte)alpha.getSample(x, y, 0);
622
/// Sets a particular bit in a byte.
624
/// <param name="bits">
625
/// the source byte </param>
626
/// <param name="index">
627
/// the index of the bit to set </param>
628
/// <param name="bit">
629
/// the value for the bit, which should be either <tt>0</tt> or
630
/// <tt>1</tt>. </param>
631
/// <param name="the">
632
/// resultant byte </param>
633
private byte SetBit(byte bits, int index, int bit)
637
bits &= (byte)~(1 << (7 - index));
641
bits |= (byte)(1 << (7 - index));
647
/// Sets a particular nibble (4 bits) in a byte.
649
/// <param name="nibbles">
650
/// the source byte </param>
651
/// <param name="index">
652
/// the index of the nibble to set </param>
653
/// <param name="the">
654
/// value for the nibble, which should be in the range
655
/// <tt>0x0..0xF</tt>. </param>
656
private byte SetNibble(byte nibbles, int index, int nibble)
658
nibbles |= (byte)(nibble << ((1 - index) * 4));
664
/// Calculates the size in bytes for a colour map with the specified bit
667
/// <param name="sBitCount">
668
/// the bit count, which represents the colour depth </param>
669
/// <returns> the size of the colour map, in bytes if <tt>sBitCount</tt> is
670
/// less than or equal to 8, otherwise <tt>0</tt> as colour maps are
671
/// only used for bitmaps with a colour depth of 8 bits or less. </returns>
672
private int GetColorMapSize(short sBitCount)
677
ret = (1 << sBitCount) * 4;