684 lines
22 KiB
C#
684 lines
22 KiB
C#
/*
|
|
Copyright (C) 2012 Volker Berlin (i-net software)
|
|
|
|
This software is provided 'as-is', without any express or implied
|
|
warranty. In no event will the authors be held liable for any damages
|
|
arising from the use of this software.
|
|
|
|
Permission is granted to anyone to use this software for any purpose,
|
|
including commercial applications, and to alter it and redistribute it
|
|
freely, subject to the following restrictions:
|
|
|
|
1. The origin of this software must not be misrepresented; you must not
|
|
claim that you wrote the original software. If you use this software
|
|
in a product, an acknowledgment in the product documentation would be
|
|
appreciated but is not required.
|
|
2. Altered source versions must be plainly marked as such, and must not be
|
|
misrepresented as being the original software.
|
|
3. This notice may not be removed or altered from any source distribution.
|
|
|
|
Jeroen Frijters
|
|
jeroen@frijters.net
|
|
|
|
*/
|
|
|
|
namespace ikvm.awt
|
|
{
|
|
|
|
using java.awt.image;
|
|
using System.IO;
|
|
using System.Drawing;
|
|
|
|
/// <summary>
|
|
/// Encodes images in ICO format.
|
|
/// </summary>
|
|
internal class IconFactory
|
|
{
|
|
|
|
/// <summary>
|
|
/// Indicates that ICO data represents an icon (.ICO).
|
|
/// </summary>
|
|
private const int TYPE_ICON = 1;
|
|
|
|
private BinaryWriter writer;
|
|
|
|
/// <summary>
|
|
/// Creates a new instance of IconFactory </summary>
|
|
internal IconFactory()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a Icon from the given list of images
|
|
/// </summary>
|
|
/// <param name="images">list of images</param>
|
|
/// <returns></returns>
|
|
internal Icon CreateIcon(java.util.List images, Size size)
|
|
{
|
|
MemoryStream stream = new MemoryStream();
|
|
Write(images, stream);
|
|
stream.Position = 0;
|
|
return new Icon(stream, size);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Encodes and writes multiple images without colour depth conversion.
|
|
/// </summary>
|
|
/// <param name="images">
|
|
/// the list of source images to be encoded </param>
|
|
/// <param name="stream">
|
|
/// the output to which the encoded image will be written </param>
|
|
internal void Write(java.util.List images, System.IO.Stream stream)
|
|
{
|
|
writer = new BinaryWriter(stream);
|
|
|
|
int count = images.size();
|
|
|
|
// file header 6
|
|
WriteFileHeader(count, TYPE_ICON);
|
|
|
|
// file offset where images start
|
|
int fileOffset = 6 + count * 16;
|
|
|
|
// icon entries 16 * count
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
BufferedImage imgc = (BufferedImage)images.get(i);
|
|
fileOffset += WriteIconEntry(imgc, fileOffset);
|
|
}
|
|
|
|
// images
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
BufferedImage imgc = (BufferedImage)images.get(i);
|
|
|
|
// info header
|
|
WriteInfoHeader(imgc);
|
|
// color map
|
|
if (imgc.getColorModel().getPixelSize() <= 8)
|
|
{
|
|
IndexColorModel icm = (IndexColorModel)imgc.getColorModel();
|
|
WriteColorMap(icm);
|
|
}
|
|
// xor bitmap
|
|
WriteXorBitmap(imgc);
|
|
// and bitmap
|
|
WriteAndBitmap(imgc);
|
|
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the ICO file header for an ICO containing the given number of
|
|
/// images.
|
|
/// </summary>
|
|
/// <param name="count">
|
|
/// the number of images in the ICO </param>
|
|
/// <param name="type">
|
|
/// TYPE_ICON
|
|
private void WriteFileHeader(int count, int type)
|
|
{
|
|
// reserved 2
|
|
writer.Write((short)0);
|
|
// type 2
|
|
writer.Write((short)type);
|
|
// count 2
|
|
writer.Write((short)count);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Encodes the <em>AND</em> bitmap for the given image according the its
|
|
/// alpha channel (transparency) and writes it to the given output.
|
|
/// </summary>
|
|
/// <param name="img">
|
|
/// the image to encode as the <em>AND</em> bitmap. </param>
|
|
private void WriteAndBitmap(BufferedImage img)
|
|
{
|
|
WritableRaster alpha = img.getAlphaRaster();
|
|
|
|
// indexed transparency (eg. GIF files)
|
|
if (img.getColorModel() is IndexColorModel && img.getColorModel().hasAlpha())
|
|
{
|
|
int w = img.getWidth();
|
|
int h = img.getHeight();
|
|
|
|
int bytesPerLine = GetBytesPerLine1(w);
|
|
|
|
byte[] line = new byte[bytesPerLine];
|
|
|
|
IndexColorModel icm = (IndexColorModel)img.getColorModel();
|
|
Raster raster = img.getRaster();
|
|
|
|
for (int y = h - 1; y >= 0; y--)
|
|
{
|
|
for (int x = 0; x < w; x++)
|
|
{
|
|
int bi = x / 8;
|
|
int i = x % 8;
|
|
// int a = alpha.getSample(x, y, 0);
|
|
int p = raster.getSample(x, y, 0);
|
|
int a = icm.getAlpha(p);
|
|
// invert bit since and mask is applied to xor mask
|
|
int b = ~a & 1;
|
|
line[bi] = SetBit(line[bi], i, b);
|
|
}
|
|
|
|
writer.Write(line);
|
|
}
|
|
}
|
|
// no transparency
|
|
else if (alpha == null)
|
|
{
|
|
int h = img.getHeight();
|
|
int w = img.getWidth();
|
|
// calculate number of bytes per line, including 32-bit padding
|
|
int bytesPerLine = GetBytesPerLine1(w);
|
|
|
|
byte[] line = new byte[bytesPerLine];
|
|
for (int i = 0; i < bytesPerLine; i++)
|
|
{
|
|
line[i] = (byte)0;
|
|
}
|
|
|
|
for (int y = h - 1; y >= 0; y--)
|
|
{
|
|
writer.Write(line);
|
|
}
|
|
}
|
|
// transparency (ARGB, etc. eg. PNG)
|
|
else
|
|
{
|
|
int w = img.getWidth();
|
|
int h = img.getHeight();
|
|
|
|
int bytesPerLine = GetBytesPerLine1(w);
|
|
|
|
byte[] line = new byte[bytesPerLine];
|
|
|
|
for (int y = h - 1; y >= 0; y--)
|
|
{
|
|
for (int x = 0; x < w; x++)
|
|
{
|
|
int bi = x / 8;
|
|
int i = x % 8;
|
|
int a = alpha.getSample(x, y, 0);
|
|
// invert bit since and mask is applied to xor mask
|
|
int b = ~a & 1;
|
|
line[bi] = SetBit(line[bi], i, b);
|
|
}
|
|
|
|
writer.Write(line);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void WriteXorBitmap(BufferedImage img)
|
|
{
|
|
Raster raster = img.getRaster();
|
|
switch (img.getColorModel().getPixelSize())
|
|
{
|
|
case 1:
|
|
Write1(raster);
|
|
break;
|
|
case 4:
|
|
Write4(raster);
|
|
break;
|
|
case 8:
|
|
Write8(raster);
|
|
break;
|
|
case 24:
|
|
Write24(raster);
|
|
break;
|
|
case 32:
|
|
Raster alpha = img.getAlphaRaster();
|
|
Write32(raster, alpha);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the <tt>InfoHeader</tt> structure to output
|
|
///
|
|
/// </summary>
|
|
private void WriteInfoHeader(BufferedImage img)
|
|
{
|
|
// Size of InfoHeader structure = 40
|
|
writer.Write(40);
|
|
// Width
|
|
writer.Write(img.getWidth());
|
|
// Height
|
|
writer.Write(img.getHeight() * 2);
|
|
// Planes (=1)
|
|
writer.Write((short)1);
|
|
// Bit count
|
|
writer.Write((short)img.getColorModel().getPixelSize());
|
|
// Compression
|
|
writer.Write(0);
|
|
// Image size - compressed size of image or 0 if Compression = 0
|
|
writer.Write(0);
|
|
// horizontal resolution pixels/meter
|
|
writer.Write(0);
|
|
// vertical resolution pixels/meter
|
|
writer.Write(0);
|
|
// Colors used - number of colors actually used
|
|
writer.Write(0);
|
|
// Colors important - number of important colors 0 = all
|
|
writer.Write(0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the <tt>IconEntry</tt> structure to output
|
|
/// </summary>
|
|
private int WriteIconEntry(BufferedImage img, int fileOffset)
|
|
{
|
|
// Width 1 byte Cursor Width (16, 32 or 64)
|
|
int width = img.getWidth();
|
|
writer.Write((byte)(width == 256 ? 0 : width));
|
|
// Height 1 byte Cursor Height (16, 32 or 64 , most commonly = Width)
|
|
int height = img.getHeight();
|
|
writer.Write((byte)(height == 256 ? 0 : height));
|
|
// ColorCount 1 byte Number of Colors (2,16, 0=256)
|
|
short BitCount = (short)img.getColorModel().getPixelSize();
|
|
int NumColors = 1 << (BitCount == 32 ? 24 : (int)BitCount);
|
|
byte ColorCount = (byte)(NumColors >= 256 ? 0 : NumColors);
|
|
writer.Write((byte)ColorCount);
|
|
// Reserved 1 byte =0
|
|
writer.Write((byte)0);
|
|
// Planes 2 byte =1
|
|
writer.Write((short)1);
|
|
// BitCount 2 byte bits per pixel (1, 4, 8)
|
|
writer.Write((short)BitCount);
|
|
// SizeInBytes 4 byte Size of (InfoHeader + ANDbitmap + XORbitmap)
|
|
int cmapSize = GetColorMapSize(BitCount);
|
|
int xorSize = GetBitmapSize(width, height, BitCount);
|
|
int andSize = GetBitmapSize(width, height, 1);
|
|
int size = 40 + cmapSize + xorSize + andSize;
|
|
writer.Write(size);
|
|
// FileOffset 4 byte FilePos, where InfoHeader starts
|
|
writer.Write(fileOffset);
|
|
return size;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the colour map resulting from the source <tt>IndexColorModel</tt>.
|
|
/// </summary>
|
|
/// <param name="icm">
|
|
/// the source <tt>IndexColorModel</tt> </param>
|
|
private void WriteColorMap(IndexColorModel icm)
|
|
{
|
|
int mapSize = icm.getMapSize();
|
|
for (int i = 0; i < mapSize; i++)
|
|
{
|
|
int rgb = icm.getRGB(i);
|
|
byte r = (byte)(rgb >> 16);
|
|
byte g = (byte)(rgb >> 8);
|
|
byte b = (byte)(rgb);
|
|
writer.Write(b);
|
|
writer.Write(g);
|
|
writer.Write(r);
|
|
writer.Write((byte)0);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates the number of bytes per line required for the given width in
|
|
/// pixels, for a 1-bit bitmap. Lines are always padded to the next 4-byte
|
|
/// boundary.
|
|
/// </summary>
|
|
/// <param name="width">
|
|
/// the width in pixels </param>
|
|
/// <returns> the number of bytes per line </returns>
|
|
private int GetBytesPerLine1(int width)
|
|
{
|
|
int ret = (int)width / 8;
|
|
if (ret % 4 != 0)
|
|
{
|
|
ret = (ret / 4 + 1) * 4;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates the number of bytes per line required for the given with in
|
|
/// pixels, for a 4-bit bitmap. Lines are always padded to the next 4-byte
|
|
/// boundary.
|
|
/// </summary>
|
|
/// <param name="width">
|
|
/// the width in pixels </param>
|
|
/// <returns> the number of bytes per line </returns>
|
|
private int GetBytesPerLine4(int width)
|
|
{
|
|
int ret = (int)width / 2;
|
|
if (ret % 4 != 0)
|
|
{
|
|
ret = (ret / 4 + 1) * 4;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates the number of bytes per line required for the given with in
|
|
/// pixels, for a 8-bit bitmap. Lines are always padded to the next 4-byte
|
|
/// boundary.
|
|
/// </summary>
|
|
/// <param name="width">
|
|
/// the width in pixels </param>
|
|
/// <returns> the number of bytes per line </returns>
|
|
private int GetBytesPerLine8(int width)
|
|
{
|
|
int ret = width;
|
|
if (ret % 4 != 0)
|
|
{
|
|
ret = (ret / 4 + 1) * 4;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates the number of bytes per line required for the given with in
|
|
/// pixels, for a 24-bit bitmap. Lines are always padded to the next 4-byte
|
|
/// boundary.
|
|
/// </summary>
|
|
/// <param name="width">
|
|
/// the width in pixels </param>
|
|
/// <returns> the number of bytes per line </returns>
|
|
private int GetBytesPerLine24(int width)
|
|
{
|
|
int ret = width * 3;
|
|
if (ret % 4 != 0)
|
|
{
|
|
ret = (ret / 4 + 1) * 4;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates the size in bytes of a bitmap with the specified size and
|
|
/// colour depth.
|
|
/// </summary>
|
|
/// <param name="w">
|
|
/// the width in pixels </param>
|
|
/// <param name="h">
|
|
/// the height in pixels </param>
|
|
/// <param name="bpp">
|
|
/// the colour depth (bits per pixel) </param>
|
|
/// <returns> the size of the bitmap in bytes </returns>
|
|
private int GetBitmapSize(int w, int h, int bpp)
|
|
{
|
|
int bytesPerLine = 0;
|
|
switch (bpp)
|
|
{
|
|
case 1:
|
|
bytesPerLine = GetBytesPerLine1(w);
|
|
break;
|
|
case 4:
|
|
bytesPerLine = GetBytesPerLine4(w);
|
|
break;
|
|
case 8:
|
|
bytesPerLine = GetBytesPerLine8(w);
|
|
break;
|
|
case 24:
|
|
bytesPerLine = GetBytesPerLine24(w);
|
|
break;
|
|
case 32:
|
|
bytesPerLine = w * 4;
|
|
break;
|
|
}
|
|
int ret = bytesPerLine * h;
|
|
return ret;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Encodes and writes raster data as a 1-bit bitmap.
|
|
/// </summary>
|
|
/// <param name="raster">
|
|
/// the source raster data </param>
|
|
private void Write1(Raster raster)
|
|
{
|
|
int bytesPerLine = GetBytesPerLine1(raster.getWidth());
|
|
|
|
byte[] line = new byte[bytesPerLine];
|
|
|
|
for (int y = raster.getHeight() - 1; y >= 0; y--)
|
|
{
|
|
for (int i = 0; i < bytesPerLine; i++)
|
|
{
|
|
line[i] = 0;
|
|
}
|
|
|
|
for (int x = 0; x < raster.getWidth(); x++)
|
|
{
|
|
int bi = x / 8;
|
|
int i = x % 8;
|
|
int index = raster.getSample(x, y, 0);
|
|
line[bi] = SetBit(line[bi], i, index);
|
|
}
|
|
|
|
writer.Write(line);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Encodes and writes raster data as a 4-bit bitmap.
|
|
/// </summary>
|
|
/// <param name="raster">
|
|
/// the source raster data </param>
|
|
private void Write4(Raster raster)
|
|
{
|
|
int width = raster.getWidth();
|
|
int height = raster.getHeight();
|
|
|
|
// calculate bytes per line
|
|
int bytesPerLine = GetBytesPerLine4(width);
|
|
|
|
// line buffer
|
|
byte[] line = new byte[bytesPerLine];
|
|
|
|
// encode and write lines
|
|
for (int y = height - 1; y >= 0; y--)
|
|
{
|
|
|
|
// clear line buffer
|
|
for (int i = 0; i < bytesPerLine; i++)
|
|
{
|
|
line[i] = 0;
|
|
}
|
|
|
|
// encode raster data for line
|
|
for (int x = 0; x < width; x++)
|
|
{
|
|
|
|
// calculate buffer index
|
|
int bi = x / 2;
|
|
|
|
// calculate nibble index (high order or low order)
|
|
int i = x % 2;
|
|
|
|
// get color index
|
|
int index = raster.getSample(x, y, 0);
|
|
// set color index in buffer
|
|
line[bi] = SetNibble(line[bi], i, index);
|
|
}
|
|
|
|
// write line data (padding bytes included)
|
|
writer.Write(line);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Encodes and writes raster data as an 8-bit bitmap.
|
|
/// </summary>
|
|
/// <param name="raster">
|
|
/// the source raster data </param>
|
|
private void Write8(Raster raster)
|
|
{
|
|
int width = raster.getWidth();
|
|
int height = raster.getHeight();
|
|
|
|
// calculate bytes per line
|
|
int bytesPerLine = GetBytesPerLine8(width);
|
|
|
|
// write lines
|
|
for (int y = height - 1; y >= 0; y--)
|
|
{
|
|
|
|
// write raster data for each line
|
|
for (int x = 0; x < width; x++)
|
|
{
|
|
|
|
// get color index for pixel
|
|
byte index = (byte)raster.getSample(x, y, 0);
|
|
|
|
// write color index
|
|
writer.Write(index);
|
|
}
|
|
|
|
// write padding bytes at end of line
|
|
for (int i = width; i < bytesPerLine; i++)
|
|
{
|
|
writer.Write((byte)0);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Encodes and writes raster data as a 24-bit bitmap.
|
|
/// </summary>
|
|
/// <param name="raster">
|
|
/// the source raster data </param>
|
|
private void Write24(Raster raster)
|
|
{
|
|
int width = raster.getWidth();
|
|
int height = raster.getHeight();
|
|
|
|
// calculate bytes per line
|
|
int bytesPerLine = GetBytesPerLine24(width);
|
|
|
|
// write lines
|
|
for (int y = height - 1; y >= 0; y--)
|
|
{
|
|
|
|
// write pixel data for each line
|
|
for (int x = 0; x < width; x++)
|
|
{
|
|
|
|
// get RGB values for pixel
|
|
byte r = (byte)raster.getSample(x, y, 0);
|
|
byte g = (byte)raster.getSample(x, y, 1);
|
|
byte b = (byte)raster.getSample(x, y, 2);
|
|
|
|
// write RGB values
|
|
writer.Write(b);
|
|
writer.Write(g);
|
|
writer.Write(r);
|
|
}
|
|
|
|
// write padding bytes at end of line
|
|
for (int i = width * 3; i < bytesPerLine; i++)
|
|
{
|
|
writer.Write((byte)0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Encodes and writes raster data, together with alpha (transparency) data,
|
|
/// as a 32-bit bitmap.
|
|
/// </summary>
|
|
/// <param name="raster">
|
|
/// the source raster data </param>
|
|
/// <param name="alpha">
|
|
/// the source alpha data </param>
|
|
private void Write32(Raster raster, Raster alpha)
|
|
{
|
|
int width = raster.getWidth();
|
|
int height = raster.getHeight();
|
|
|
|
// write lines
|
|
for (int y = height - 1; y >= 0; y--)
|
|
{
|
|
|
|
// write pixel data for each line
|
|
for (int x = 0; x < width; x++)
|
|
{
|
|
|
|
// get RGBA values
|
|
byte r = (byte)raster.getSample(x, y, 0);
|
|
byte g = (byte)raster.getSample(x, y, 1);
|
|
byte b = (byte)raster.getSample(x, y, 2);
|
|
byte a = (byte)alpha.getSample(x, y, 0);
|
|
|
|
// write RGBA values
|
|
writer.Write(b);
|
|
writer.Write(g);
|
|
writer.Write(r);
|
|
writer.Write(a);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets a particular bit in a byte.
|
|
/// </summary>
|
|
/// <param name="bits">
|
|
/// the source byte </param>
|
|
/// <param name="index">
|
|
/// the index of the bit to set </param>
|
|
/// <param name="bit">
|
|
/// the value for the bit, which should be either <tt>0</tt> or
|
|
/// <tt>1</tt>. </param>
|
|
/// <param name="the">
|
|
/// resultant byte </param>
|
|
private byte SetBit(byte bits, int index, int bit)
|
|
{
|
|
if (bit == 0)
|
|
{
|
|
bits &= (byte)~(1 << (7 - index));
|
|
}
|
|
else
|
|
{
|
|
bits |= (byte)(1 << (7 - index));
|
|
}
|
|
return bits;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets a particular nibble (4 bits) in a byte.
|
|
/// </summary>
|
|
/// <param name="nibbles">
|
|
/// the source byte </param>
|
|
/// <param name="index">
|
|
/// the index of the nibble to set </param>
|
|
/// <param name="the">
|
|
/// value for the nibble, which should be in the range
|
|
/// <tt>0x0..0xF</tt>. </param>
|
|
private byte SetNibble(byte nibbles, int index, int nibble)
|
|
{
|
|
nibbles |= (byte)(nibble << ((1 - index) * 4));
|
|
|
|
return nibbles;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates the size in bytes for a colour map with the specified bit
|
|
/// count.
|
|
/// </summary>
|
|
/// <param name="sBitCount">
|
|
/// the bit count, which represents the colour depth </param>
|
|
/// <returns> the size of the colour map, in bytes if <tt>sBitCount</tt> is
|
|
/// less than or equal to 8, otherwise <tt>0</tt> as colour maps are
|
|
/// only used for bitmaps with a colour depth of 8 bits or less. </returns>
|
|
private int GetColorMapSize(short sBitCount)
|
|
{
|
|
int ret = 0;
|
|
if (sBitCount <= 8)
|
|
{
|
|
ret = (1 << sBitCount) * 4;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
}
|
|
}
|