HomeForumSourceResearchGuide
Sign in to contribute to source. how it works
Component media.image.ImageEncoder:ico by barry
expand copy to clipboardexpand
//NOTE: this implementation supports only PNG-encoded ico files; not all ico-compatible programs are capable of reading this format
// - the alternative is BMP-and-mask-encoded, which is not yet implemented; in future we might select the encoding to use based on the dimensions of the ico (and support decoding of any format)

data IconDir {
	int2 reserved
	const int2 TYPE_ICO = 1
	const int2 TYPE_CUR = 2
	int2 type
	int2 entries
}

data IconDirEntry {
	int1 width
	int1 height
	int1 palette
	int1 reserved
	int2 colorPlanes
	int2 bitsPerPixel
	int4 imageSizeBytes
	int4 imageOffsetBytes
}

data Int2 {
	int2 n
	}

data Int4 {
	int4 n
	}

component provides ImageEncoder:ico requires io.File:mem, ImageEncoder:png, io.Output out, data.IntUtil iu {
	
	PixelMap map
	
	ImageEncoder:ImageEncoder()
		{
		
		}
	
	PixelMap ImageEncoder:getPixels()
		{
		return map
		}
	
	void ImageEncoder:setPixels(PixelMap pm)
		{
		map = pm
		}
	
	bool ImageEncoder:loadImage(File fd)
		{
		IconDir icd = new IconDir()

		byte stream[] = dana.serial(icd)

		byte buf[] = fd.read(stream.arrayLength)
		if (buf != stream.arrayLength) throw new Exception("insufficient header bytes read from file")
		stream =[] buf

		IconDirEntry entries[] = new IconDirEntry[icd.entries]

		for (int i = 0; i < entries.arrayLength; i++)
			{
			IconDirEntry entry = new IconDirEntry()
			stream = dana.serial(entry)

			buf = fd.read(stream.arrayLength)
			if (buf != stream.arrayLength) throw new Exception("insufficient header bytes read from file")
			stream =[] buf

			entries[i] = entry
			}
		
		//now we just read the image data for the first entry, and decode it (currently only supporting PNG encoding)
		fd.setPos(entries[0].imageOffsetBytes)

		buf = fd.read(entries[0].imageSizeBytes)
		if (buf.arrayLength != entries[0].imageSizeBytes) throw new Exception("insufficient content bytes read from file")

		File mfd = new File:mem(null, File.WRITE)
		mfd.write(buf)
		mfd.setPos(0)
		ImageEncoder decoder = new ImageEncoder:png()
		decoder.loadImage(mfd)

		map = decoder.getPixels()
		
		return true
		}
	
	void reverseEndian(byte num[])
		{
		int j = num.arrayLength - 1
		for (int i = 0; i < num.arrayLength / 2; i++)
			{
			byte tmp = num[j]
			num[j] = num[i]
			num[i] = tmp
			j --
			}
		}
	
	int2 reverseInt2(int2 i2)
		{
		Int2 ct = new Int2(i2)
		reverseEndian(dana.serial(ct))
		return ct.n
		}
	
	int4 reverseInt4(int4 i4)
		{
		Int4 ct = new Int4(i4)
		reverseEndian(dana.serial(ct))
		return ct.n
		}

	bool ImageEncoder:saveImage(File fd)
		{
		//encode the image as a PNG
		File mfd = new File:mem(null, File.WRITE)
		ImageEncoder encoder = new ImageEncoder:png()
		encoder.setPixels(map)
		encoder.saveImage(mfd)

		//write the file header
		IconDir icd = new IconDir()
		icd.type = reverseInt2(IconDir.TYPE_ICO)
		icd.entries = reverseInt2(1)

		byte stream[] = dana.serial(icd)

		fd.write(stream)

		//write the (single) directory entry
		IconDirEntry entry = new IconDirEntry()
		stream = dana.serial(entry)
		if (map.size.width < 256)
			entry.width = map.size.width
			else
			entry.width = 0
		if (map.size.height < 256)
			entry.width = map.size.height
			else
			entry.width = 0
		entry.bitsPerPixel = reverseInt2(32)
		entry.imageSizeBytes = reverseInt4(mfd.getSize())
		entry.imageOffsetBytes = reverseInt4(fd.getPos() + stream.arrayLength)

		fd.write(stream)

		//write the PNG-encoded image
		mfd.setPos(0)
		fd.write(mfd.read(mfd.getSize()))

		return true
		}
	
	}
Revision history
To propose a new revision to this entity, use dana source put -uc your/new/version.dn -n media.image.ImageEncoder:ico -m "reason for update" -u yourUsername
Version 2 by barry
Version 1 (this version) by barry
Notes for this version: Standard Library Initialisation