<!DOCTYPE html>
<meta charset=utf-8>
<body style="background:#cff">
<p>
Semitransparent cursor test. The gradients blend from opaque to clear. The arrow cursors are opaque. Mouseover or download cur file.
<p><a href="https://connect.microsoft.com/IE/feedback/details/781016/im-unable-to-create-cursors-using-a-data-uri">IE 10 does not support data uris for cursors</a> or in links, so download these cursors and test within in Windows. Or just test in other browser within Windows.
<script>
// reference: the old new thing 4-part series http://blogs.msdn.com/b/oldnewthing/archive/2010/10/18/10077133.aspx
if (true) (function() {
var canvas = document.createElement("canvas");
canvas.width = canvas.height = 32;
var c = canvas.getContext("2d");
var grad = c.createLinearGradient(0,0,32,32);
grad.addColorStop(0, 'blue');
grad.addColorStop(1, 'rgba(0,255,0,0)');
c.fillStyle = grad;
c.fillRect(0,0,32,32);
addIconToBody(canvas, "gradient", 0, 0);
})();
if (true) (function() {
var canvas = document.createElement("canvas");
canvas.width = canvas.height = 32;
var c = canvas.getContext("2d");
var grad = c.createLinearGradient(32,32,0,0);
grad.addColorStop(0, 'blue');
grad.addColorStop(1, 'rgba(0,255,0,0)');
c.fillStyle = grad;
c.fillRect(0,0,32,32);
addIconToBody(canvas, "upsidedowngradient", 31, 31);
})();
if (true) (function() {
var canvas = document.createElement("canvas");
canvas.width = canvas.height = 32;
var c = canvas.getContext("2d");
var grad = c.createLinearGradient(0,0,32,0);
grad.addColorStop(0, 'blue');
grad.addColorStop(1, 'rgba(0,255,0,0)');
c.fillStyle = grad;
c.fillRect(0,0,32,32);
addIconToBody(canvas, "righttoleftgradient", 0, 0);
})();
addTriangleIcon("red", "red");
addTriangleIcon("green", "green");
addTriangleIcon("blue", "blue");
function addTriangleIcon(color, name) {
var canvas = document.createElement("canvas");
canvas.width = canvas.height = 32;
var c = canvas.getContext("2d");
c.fillStyle = color;
c.beginPath();
c.moveTo(0,0);
c.lineTo(0,32);
c.lineTo(20,25);
c.closePath();
c.fill();
addIconToBody(canvas, name, 0, 0);
}
function addIconToBody(canvas, title, hotspotX, hotspotY) {
var iconBuffer = toIcon(canvas, hotspotX, hotspotY);
var a = document.createElement("a");
// what's the right mime type? image/x-icon image/x-win-bitmap application/cur
var iconBufferBase64 = btoa(String.fromCharCode.apply(null, new Uint8Array(iconBuffer)));
var url = "data:image/x-icon;base64," + iconBufferBase64;
var pngurl = canvas.toDataURL();
a.href = url;
a.download = title + ".cur";
a.textContent = title + ".cur";
var div = document.createElement("div");
var cssCursor =
"url('" + url + "'), " +
//"url(" + pngurl + ") " + hotspotX + " " + hotspotY + ", " +
"auto";
div.style.cursor = cssCursor;
div.appendChild(canvas)
div.appendChild(a);
document.body.appendChild(div)
}
/** Convert any canvas into a cursor.
* TODO: throw errors when the canvas is too big.
* TODO: allow specifying AND mask.
*/
function toIcon(canvas, hotspotX, hotspotY) {
var c = canvas.getContext("2d");
var imageData = c.getImageData(0,0,canvas.width, canvas.height);
imageData.data // rgba uint8clampedarray
var icondirentrySize = 1+1+1+1 + 2+2 + 4+4;
var icondirSize = 4 + 4 + 4 + 1*icondirentrySize;
var bitmapinfoheaderSize = 4*3 + 2+2 + 4*6;
var iconimageSize = bitmapinfoheaderSize +
0 +
4*imageData.width*imageData.height +
Math.floor((imageData.width+31)/32)*4*imageData.height;
var iconBuffer = new ArrayBuffer(icondirSize + iconimageSize);
var iconimageOffset = icondirSize;
var iconDirDataView = new DataView(iconBuffer, 0, icondirSize);
fillIconDir(iconDirDataView, imageData, iconimageSize, iconimageOffset, hotspotX, hotspotY);
var iconDirDataView = new DataView(iconBuffer, iconimageOffset, iconimageSize);
fillIconImage(iconDirDataView, imageData);
return iconBuffer;
}
function fillIconDir(dv, imageData, iconimageSize, iconimageOffset, hotspotX, hotspotY) {
dv.setUint16(0, 0, true); // idReserved=0
dv.setUint16(2, 2, true); // idType=2 for cursors, 1 for icons
dv.setUint16(4, 1, true); // idCount of images
fillIconDirEntry(new DataView(dv.buffer, dv.byteOffset+6, dv.byteLength-6), imageData, iconimageSize, iconimageOffset, hotspotX, hotspotY);
}
function fillIconDirEntry(dv, imageData, iconimageSize, iconimageOffset, hotspotX, hotspotY) {
dv.setUint8(0, imageData.width);
dv.setUint8(1, imageData.height);
dv.setUint8(2, 0); // bColorCount of color table is 0 for 32bpp
dv.setUint8(3, 0);
if (true) { // cursor hotspot from upper left
// TODO: get reference for this
dv.setUint16(4, hotspotX, true);
dv.setUint16(6, hotspotY, true);
} else { // icon
dv.setUint16(4, 1, true); // wPlanes = 1; it was used for EGA displays; see old new thing.
dv.setUint16(6, 32, true); // wBitCount
}
dv.setUint32(8, iconimageSize, true);
dv.setUint32(12, iconimageOffset, true);
}
function fillIconImage(dv, imageData) {
var bitmapinfoheaderSize = 4*3 + 2+2 + 4*6;
dv.setUint32(0, bitmapinfoheaderSize, true);
dv.setInt32(4, imageData.width, true);
dv.setInt32(8, imageData.height*2, true); // biHeight of combined masks, positive for bottom-up.
dv.setInt16(12, 1, true); // biPlanes=1; it was used for EGA displays; see old new thing.
dv.setInt16(14, 32, true); // biBitCount
dv.setUint32(16, 0, true); // biCompression=BI_RGB (0)
var biSizeImage =
4*imageData.width*imageData.height +
Math.floor((imageData.width+31)/32)*4*imageData.height;
dv.setUint32(20, biSizeImage, true); // biSizeImage. I think this should include both AND and XOR.images.
dv.setInt32(24, 0, true);
dv.setInt32(28, 0, true);
dv.setUint32(32, 0, true);
dv.setUint32(36, 0, true);
// XOR mask, 32bpp ARGB bottom-up
// note that imageData.data is RGBA top-down.
var xorBegin = 40;
var andBegin = xorBegin + 4*imageData.width*imageData.height;
var xorRow = xorBegin + 4*imageData.width*(imageData.height-1);
var andRow = andBegin + Math.floor((imageData.width+31)/32)*4*(imageData.height-1);
var imageDataRow = 0;
for (var y = 0; y < imageData.width; y++) {
var andBit = 0x80;
for (var x = 0; x < imageData.height; x++) {
var imageDataPos = imageDataRow + 4*x;
var xorPos = xorRow + 4*x;
var andByte = andRow + Math.floor(x/8);
var
r = imageData.data[imageDataPos + 0],
g = imageData.data[imageDataPos + 1],
b = imageData.data[imageDataPos + 2],
a = imageData.data[imageDataPos + 3];
dv.setUint8(xorPos + 0, b);
dv.setUint8(xorPos + 1, g);
dv.setUint8(xorPos + 2, r);
dv.setUint8(xorPos + 3, a);
var showBackground = a < 0x80;
dv.setUint8(andByte, dv.getUint8(andByte) | (showBackground?andBit:0));
andBit = (andBit >> 1 | andBit << 7) & 0xff;
}
xorRow -= 4*imageData.width;
andRow -= Math.floor((imageData.width+31)/32)*4;
imageDataRow += 4*imageData.width;
}
}
/**
typedef struct
{
WORD idReserved; // Reserved (must be 0)
WORD idType; // Resource Type (1 for icons)
WORD idCount; // How many images?
ICONDIRENTRY idEntries[1]; // An entry for each image (idCount of 'em) Q: is there padding before this? I don't thinkso.
} ICONDIR, *LPICONDIR;
*/
/**
typedef struct
{
BYTE bWidth; // Width, in pixels, of the image
BYTE bHeight; // Height, in pixels, of the image
BYTE bColorCount; // Number of colors in image (1<<(wBitCount*wPlanes), or 0 if >=8bpp (wBitCount*wPlanes>=8))
BYTE bReserved; // Reserved ( must be 0)
WORD xhotspot // ico: wPlanes; // Color Planes
WORD yhotspot from top // ico: wBitCount; // Bits per pixel
DWORD dwBytesInRes; // How many bytes in this resource?
DWORD dwImageOffset; // Where in the file is this image?
} ICONDIRENTRY, *LPICONDIRENTRY;
typdef struct
{
BITMAPINFOHEADER icHeader; // DIB header
RGBQUAD icColors[1]; // Color table; empty for biBitCount=32
BYTE icXOR[1]; // DIB bits for XOR mask (0RGB, or ARGB). DIBs are bottom-up.
BYTE icAND[1]; // DIB bits for AND mask. all rows padded to 32 bits (i.e. row size = (biBitCount*biWidth+31)/32*4).
} ICONIMAGE, *LPICONIMAGE;
// (device independent bitmap)
typedef struct tagBITMAPINFOHEADER {
DWORD biSize; // sizeof of this header
LONG biWidth; // The width of the bitmap, in pixels.
LONG biHeight; // COMBINED height of XOR and AND pixels of icon. Positive for bottom-up, negative for top-down
WORD biPlanes; // must be 1
WORD biBitCount; // bits per pixel e.g. 32
DWORD biCompression; // e.g. PNG but must be BI_RGB=0 for icon? (or BI_BITFIELDS=3 according to old new thing?)
DWORD biSizeImage; // byte size; may be 0 for BI_RGB
LONG biXPelsPerMeter; // must be 0
LONG biYPelsPerMeter; // must be 0
DWORD biClrUsed; // must be 0?
DWORD biClrImportant; // must be 0?
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;
*/
</script>