Related
I'm using a custom cursor that changes when the user hovers on different data-types. It is currently scaling up, but I also need to change the opacity to 0.7, I am unsure how to amend the following code to achieve this, although I assume it would be by adding to this line of code:
const keyframes = {
transform: `translate(${x}px, ${y}px) scale(${interacting ? 5 : 1})`
}
FULL CODE
const cursor = document.getElementById("cursor");
const animateCursor = (e, interacting) => {
const x = e.clientX - cursor.offsetWidth / 2,
y = e.clientY - cursor.offsetHeight / 2;
const keyframes = {
transform: `translate(${x}px, ${y}px) scale(${interacting ? 5 : 1})`
}
cursor.animate(keyframes, {
duration: 800,
fill: "forwards"
});
}
const getCursorClass = type => {
switch(type) {
case "video":
return "fas fa-play";
case "drag":
return "drag";
default:
return "fas fa-arrow-up-right";
}
}
window.onmousemove = e => {
const interactable = e.target.closest(".interactable"),
interacting = interactable !== null;
const icon = document.getElementById("cursor-icon");
animateCursor(e, interacting);
cursor.dataset.type = interacting ? interactable.dataset.type : "";
if(interacting) {
icon.className = getCursorClass(interactable.dataset.type);
}
}
so I was playing around with the fabricjs canvas library and I found this fiddle written in vanillajs which lets you draw polygons on the canvas. I wanted to implement this exact thing in my react project so I tried to convert the entire code into react (https://codesandbox.io/s/jolly-kowalevski-tjt58). The code works somewhat but there are some new bugs which are not in the original fiddle and I'm having trouble fixing them.
for eg: try to create a polygon by clicking the draw button, when you do this first time, the polygon is drawn without any bugs, but when you click the draw button again for the second time, the canvas starts acting weird and a weird polygon is created.
So basically I need help in converting the vanilla code to react with 0 bugs.
extra information:
fabric version used in the fiddle: 4.0.0
fabric version in sandbox: 4.0.0
Vanilla Js Code:
const getPathBtn = document.getElementById("get-path");
const drawPolygonBtn = document.getElementById("draw-polygon");
const showPolygonBtn = document.getElementById("show-polygon");
const editPolygonBtn = document.getElementById("edit-polygon");
const canvas = new fabric.Canvas("canvas", {
selection: false
});
let line, isDown;
let prevCords;
let vertices = [];
let polygon;
const resetCanvas = () => {
canvas.off();
canvas.clear();
};
const resetVariables = () => {
line = undefined;
isDown = undefined;
prevCords = undefined;
polygon = undefined;
vertices = [];
};
const addVertice = (newPoint) => {
if (vertices.length > 0) {
const lastPoint = vertices[vertices.length - 1];
if (lastPoint.x !== newPoint.x && lastPoint.y !== newPoint.y) {
vertices.push(newPoint);
}
} else {
vertices.push(newPoint);
}
};
const drawPolygon = () => {
resetVariables();
resetCanvas();
canvas.on("mouse:down", function(o) {
isDown = true;
const pointer = canvas.getPointer(o.e);
let points = [pointer.x, pointer.y, pointer.x, pointer.y];
if (prevCords && prevCords.x2 && prevCords.y2) {
const prevX = prevCords.x2;
const prevY = prevCords.y2;
points = [prevX, prevY, prevX, prevY];
}
const newPoint = {
x: points[0],
y: points[1]
};
addVertice(newPoint);
line = new fabric.Line(points, {
strokeWidth: 2,
fill: "black",
stroke: "black",
originX: "center",
originY: "center",
});
canvas.add(line);
});
canvas.on("mouse:move", function(o) {
if (!isDown) return;
const pointer = canvas.getPointer(o.e);
const coords = {
x2: pointer.x,
y2: pointer.y
};
line.set(coords);
prevCords = coords;
canvas.renderAll();
});
canvas.on("mouse:up", function(o) {
isDown = false;
const pointer = canvas.getPointer(o.e);
const newPoint = {
x: pointer.x,
y: pointer.y
};
addVertice(newPoint);
});
canvas.on("object:moving", function(option) {
const object = option.target;
canvas.forEachObject(function(obj) {
if (obj.name == "Polygon") {
if (obj.PolygonNumber == object.polygonNo) {
const points = window["polygon" + object.polygonNo].get(
"points"
);
points[object.circleNo - 1].x = object.left;
points[object.circleNo - 1].y = object.top;
window["polygon" + object.polygonNo].set({
points: points,
});
}
}
});
canvas.renderAll();
});
};
const showPolygon = () => {
resetCanvas();
if (!polygon) {
polygon = new fabric.Polygon(vertices, {
fill: "transparent",
strokeWidth: 2,
stroke: "black",
objectCaching: false,
transparentCorners: false,
cornerColor: "blue",
});
}
polygon.edit = false;
polygon.hasBorders = true;
polygon.cornerColor = "blue";
polygon.cornerStyle = "rect";
polygon.controls = fabric.Object.prototype.controls;
canvas.add(polygon);
};
// polygon stuff
// define a function that can locate the controls.
// this function will be used both for drawing and for interaction.
function polygonPositionHandler(dim, finalMatrix, fabricObject) {
let x = fabricObject.points[this.pointIndex].x - fabricObject.pathOffset.x,
y = fabricObject.points[this.pointIndex].y - fabricObject.pathOffset.y;
return fabric.util.transformPoint({
x: x,
y: y
},
fabric.util.multiplyTransformMatrices(
fabricObject.canvas.viewportTransform,
fabricObject.calcTransformMatrix()
)
);
}
// define a function that will define what the control does
// this function will be called on every mouse move after a control has been
// clicked and is being dragged.
// The function receive as argument the mouse event, the current trasnform object
// and the current position in canvas coordinate
// transform.target is a reference to the current object being transformed,
function actionHandler(eventData, transform, x, y) {
let polygon = transform.target,
currentControl = polygon.controls[polygon.__corner],
mouseLocalPosition = polygon.toLocalPoint(
new fabric.Point(x, y),
"center",
"center"
),
polygonBaseSize = polygon._getNonTransformedDimensions(),
size = polygon._getTransformedDimensions(0, 0),
finalPointPosition = {
x: (mouseLocalPosition.x * polygonBaseSize.x) / size.x +
polygon.pathOffset.x,
y: (mouseLocalPosition.y * polygonBaseSize.y) / size.y +
polygon.pathOffset.y,
};
polygon.points[currentControl.pointIndex] = finalPointPosition;
return true;
}
// define a function that can keep the polygon in the same position when we change its
// width/height/top/left.
function anchorWrapper(anchorIndex, fn) {
return function(eventData, transform, x, y) {
let fabricObject = transform.target,
absolutePoint = fabric.util.transformPoint({
x: fabricObject.points[anchorIndex].x -
fabricObject.pathOffset.x,
y: fabricObject.points[anchorIndex].y -
fabricObject.pathOffset.y,
},
fabricObject.calcTransformMatrix()
),
actionPerformed = fn(eventData, transform, x, y),
newDim = fabricObject._setPositionDimensions({}),
polygonBaseSize = fabricObject._getNonTransformedDimensions(),
newX =
(fabricObject.points[anchorIndex].x -
fabricObject.pathOffset.x) /
polygonBaseSize.x,
newY =
(fabricObject.points[anchorIndex].y -
fabricObject.pathOffset.y) /
polygonBaseSize.y;
fabricObject.setPositionByOrigin(absolutePoint, newX + 0.5, newY + 0.5);
return actionPerformed;
};
}
function editPolygon() {
canvas.setActiveObject(polygon);
polygon.edit = true;
polygon.hasBorders = false;
let lastControl = polygon.points.length - 1;
polygon.cornerStyle = "circle";
polygon.cornerColor = "rgba(0,0,255,0.5)";
polygon.controls = polygon.points.reduce(function(acc, point, index) {
acc["p" + index] = new fabric.Control({
positionHandler: polygonPositionHandler,
actionHandler: anchorWrapper(
index > 0 ? index - 1 : lastControl,
actionHandler
),
actionName: "modifyPolygon",
pointIndex: index,
});
return acc;
}, {});
canvas.requestRenderAll();
}
// Button events
drawPolygonBtn.onclick = () => {
drawPolygon();
};
showPolygonBtn.onclick = () => {
showPolygon();
};
editPolygonBtn.onclick = () => {
editPolygon();
};
getPathBtn.onclick = () => {
console.log("vertices", polygon.points);
};
On 2nd draw (click the draw button again for the second time), the line is always connected to same point. So there is a problem with prevCords.
By adding a console.log to handler function of "mouse:mouse" confirmed above statement:
fabricCanvas.on("mouse:move", function (o) {
console.log("mousemove fired", prevCords); // always the same value
if (isDown.current || !line.current) return;
const pointer = fabricCanvas.getPointer(o.e);
const coords = {
x2: pointer.x,
y2: pointer.y
};
line.current.set(coords);
setPrevCords(coords); // the line should connect to this new point
fabricCanvas.renderAll();
});
It's because of closure, the function handler of mouse:move will always remember the value of prevCords when it was created (i.e when you click on Draw button) not the value that was updated by setPrevCords
To solve above problem, simply use useRef to store prevCords (or use reference)
Line 6:
const [fabricCanvas, setFabricCanvas] = useState();
const prevCordsRef = useRef();
const line = useRef();
Line 35:
const resetVariables = () => {
line.current = undefined;
isDown.current = undefined;
prevCordsRef.current = undefined;
polygon.current = undefined;
vertices.current = [];
};
Line 65:
if (prevCordsRef.current && prevCordsRef.current.x2 && prevCordsRef.current.y2) {
const prevX = prevCordsRef.current.x2;
const prevY = prevCordsRef.current.y2;
points = [prevX, prevY, prevX, prevY];
}
Line 96:
prevCordsRef.current = coords;
One last suggestion is to change Line 89 (so the feature match the demo):
if (!isDown.current) return;
On summary:
Don't use useState for variable that must have latest value in another function handler. Use useRef instead
Use useState for prevCords is a wasted since React will re-render on every setState
I'd like to use the tumblr API with JS to draw the latest posted images to blog with mouse movement.
I've successfully got it drawing HTML image links but need some help implementing the API images.
I've made a JSfiddle - I'm looking to replace the two tree images with the API loaded images, the aim is to have all of the API images (20) to draw on with the mouse movement. In the fiddle I have loaded the API images onto the page to check they are being received properly.
https://jsfiddle.net/Hevering123/xpvt214o/721906/
// Tumblr API load -
$.ajax({
url: "https://api.tumblr.com/v2/blog/typophile.tumblr.com/posts?api_key=sNCvOfqUTzUJzBOViCbYfkaGeQaFAS4Q4XNtHMu8YPo6No3OiY",
dataType: 'jsonp',
success: function(posts){
var postings = posts.response.posts;
var text = '';
for (var i in postings) {
var p = postings[i];
var type = p.type;
if(type == 'photo'){
text += '<li><img src=' + p.photos[0].original_size.url +'></li>';
}else if(type == 'text'){
text += '<li>' +p.body+ '</li>';
}else{
text += '<li>Some other post type<li>';
}
console.log(type);
}
$('ul').append(text);
}
});
Below is the draw image JS
// draw image function
function resizeCanvas() {
canvas.width = window.innerWidth,
canvas.height = window.innerHeight,
drawStuff()
}
function drawStuff() {
function e() {
new Date - a < p ? setTimeout(e, p) : (g = !1,
context.clearRect(0, 0, canvas.width, canvas.height))
}
// It's the below code specifically that I'm completely stuck with, how do I replace the local img pngs with the correct JS to call in the API tumblr images
var i = ["https://www.fast-growing-trees.com/images/P/Sawtooth-Oak_450_a.jpg", "https://cdn.allbirds.com/image/fetch/q_auto,f_auto/q_auto,f_auto,b_rgb:fff,w_500/https://cdn.shopify.com/s/files/1/1104/4168/files/PDP_Plant_1_1.png%3Fv%3D1520805044" ]
, t = {
x: 0,
y: 0
}
, n = 80
, _ = 0;
isMobile && (n = 1);
var a, g = !1, p = 2500;
jQuery("#bg-canvas").on("mousemove touchend", function(s) {
if (jQuery(".move").hide(),
a = new Date,
g === !1 && (g = !0,
setTimeout(e, p)),
s.pageX - t.x > n || s.pageX - t.x < -n || s.pageY - t.y > n || s.pageY - t.y < -n) {
t.x = s.pageX,
t.y = s.pageY;
var o = new Image;
o.onload = function() {
var e = .5 * this.width
, i = .5 * this.height;
context.drawImage(o, s.pageX - e, s.pageY - i)
}
,
_ < i.length - 1 ? (_++,
o.src = i[_]) : (_ = 0,
o.src = i[_])
}
}),
jQuery("header p")({
})
}
var MD = [].join("\n");
console.log(MD, "color: white; background-color: black;");
var isMobile = !1;
"ontouchstart"in document.documentElement && (jQuery(".move").text("tap"),
isMobile = !0);
var canvas = document.getElementById("bg-canvas")
, context = canvas.getContext("2d");
window.addEventListener("resize", resizeCanvas, !1),
resizeCanvas();
Any help is very appreciated!
This is the code I am using. (code is at way bottom of this post but here is link to GitHubGist :: Noitidart / _ff-addon-snippet-browseForBadgeThenCreateSaveAnApply.js) It is copy pastatble to scratchpad (i tried fiddle but it needs privelage scope). When you run it will ask you to select a 16x16 image. Then it will take the firefox icon and put it on a canvas and then take the icon you browsed to and overlay it on the bottom right. Then it will convert it to .ico and save to your desktop as profilist16.ico and profilist32.ico. It will then change the icons of all your firefox windows.
After you do the above, please open a new firefox window and then in alt+tab you'll see the firefox logo of the badged icon is dirtier.
On the bottom you see the original canvas drawing (it looks blurry but i think thats my zoom level on firefox). The icon is crisp but if you notice the badged icon (on right) on the edges (especially top) you see dirt, like black jagged stuff which is not seen in the usual icon (at left)
var win = Services.wm.getMostRecentWindow(null);
var me = win;
//these should be global vars
var sizes = []; //os dependent
var img = {}; //holds Image for each size image
var osIconFileType = 'ico'; //os dependent
var cOS = 'Windows';
function badgeIt() {
var fp = Cc["#mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
fp.init(win, "Select Badge Image", Ci.nsIFilePicker.modeOpen);
var fpCallback = function(rv) {
if (rv == Ci.nsIFilePicker.returnOK || rv == Ci.nsIFilePicker.returnReplace) {
if (sizes.length == 0) {
//figure out what os this is and populate sizes withthe sizes needed for this os
sizes = [32, 16]; //note: ask on SO how to determine what sizes the os uses for its icons?
}
loadBadgeImage();
} else {
//user did not select an file to badge with
}
}
var ranOnce0 = false;
var checkAllDefaultImagesLoaded = function() {
for (var i=0; i<sizes.length; i++) {
//console.log('img.sizes[i].loaded for i = ' + sizes[i] + ' is == ' + uneval(img[sizes[i]]));
if (!img[sizes[i]] || !img[sizes[i]].loaded) {
console.log('returning false as sizes[i]', sizes[i], 'is not loaded yet')
return false; //return as not yet all are done
}
//me.alert('all img sizes loaded');
}
//ok all sizes loaded
if (ranOnce0) {
alert('already ranOnce0 so return false');
return false;
}
ranOnce0 = true;
return true;
}
var loadDefaultImages = function() {
for (var i=0; i<sizes.length; i++) {
img[sizes[i]] = {};
img[sizes[i]].Image = new Image();
img[sizes[i]].Image.onload = function(iBinded) {
console.log('i', iBinded);
//console.log('img', img);
console.log('sizes[i]', sizes[iBinded]);
console.log('img[sizes[iBinded]].loaded=', uneval(img[sizes[iBinded]]), 'will now set it to true')
img[sizes[iBinded]].loaded = true;
console.log('just loaded size of (sizes[iBinded]) = ' + sizes[iBinded]);
var allLoaded = checkAllDefaultImagesLoaded();
if (allLoaded == true) {
console.log('allLoaded == true so createAndSave')
createAndSaveIcons();
} else {
console.warn('allLoaded is false so dont create')
}
}.bind(null, i)
img[sizes[i]].Image.src = 'chrome://branding/content/icon' + sizes[i] + '.png';
}
}
var loadBadgeImage = function() {
console.log('loadBadgeImage')
img.badge = {};
img.badge.Image = new Image();
img.badge.Image.onload = function() {
console.log('bagde image loaded')
img.badge.loaded = true;
if (checkAllDefaultImagesLoaded()) {
console.log('all dfault images PRELOADED so continue to createAndSaveIcons')
createAndSaveIcons();
} else {
console.log('all default images not loaded so start loading them')
loadDefaultImages();
}
}
img.badge.Image.src = Services.io.newFileURI(fp.file).spec;
}
var badgedIconMade = {};
var ranOnce = false;
var checkAllBadgedIconsMade = function() {
for (var i=0; i<sizes.length; i++) {
if (!badgedIconMade[sizes[i]]) {
return; //not yt done making
}
}
if (ranOnce) {
alert('already ranOnce so return');
return;
}
ranOnce = true;
// all badged icons made
applyIcons();
}
var blobCallback = function(size) {
return function (b) {
var r = new FileReader();
r.onloadend = function () {
// r.result contains the ArrayBuffer.
//alert(r.result)
img[size].ArrayBuffer = r.result;
badgedIconMade[size] = true;
//checkAllBadgedIconsMade();
Cu.import('resource://gre/modules/osfile.jsm');
var writePath = OS.Path.join(OS.Constants.Path.desktopDir, 'profilist' + size + '.' + osIconFileType);
console.log('writePath', writePath)
var promise = OS.File.writeAtomic(writePath, new Uint8Array(r.result), {tmpPath:writePath + '.tmp'});
promise.then(
function() {
//win.alert('success')
checkAllBadgedIconsMade();
},
function() {
//win.alert('failure')
}
);
};
//var url = window.URL.createObjectURL(b)
//img[size].blobUrl = url;
//prompt('', url)
r.readAsArrayBuffer(b);
}
}
var createAndSaveIcons = function() {
console.log('createAndSave')
var canvas = document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');
var ctx = canvas.getContext('2d');
gBrowser.contentDocument.documentElement.appendChild(canvas);
var badgeDim = { //holds key which is size of default icon, and the value is the dimension to draw the badge for that default icon size //this is set by me the dev, maybe make preference for this for user
'16': 10,
'32': 16
};
for (var i=0; i<sizes.length; i++) {
canvas.width = sizes[i];
canvas.height = sizes[i];
ctx.clearRect(0, 0, sizes[i], sizes[i]);
ctx.drawImage(img[sizes[i]].Image, 0, 0);
if (sizes[i] in badgeDim) {
if (badgeDim[sizes[i]] != sizes[i]) { //before i had `img.badge.Image.width` in place of `sizes[i]`, but can just use sizes[i] because thats the dim of the default icon duh
ctx.drawImage(img.badge.Image, sizes[i]-badgeDim[sizes[i]], sizes[i]-badgeDim[sizes[i]], badgeDim[sizes[i]], badgeDim[sizes[i]]);
} else {
//the redim size is same as icon size anyways so just draw it
ctx.drawImage(img.badge.Image, sizes[i]-badgeDim[sizes[i]], sizes[i]-badgeDim[sizes[i]]);
}
} else {
//sizes[i] is not in badgeDim meaning i dont care what size the badge is on this size of icon
ctx.drawImage(img.badge.Image, sizes[i]-badgeDim[sizes[i]], sizes[i]-badgeDim[sizes[i]]);
}
//canvas.mozFetchAsStream(mfasCallback(sizes[i]), 'image/vnd.microsoft.icon')
canvas.toBlob(blobCallback(sizes[i]), "image/vnd.microsoft.icon", "-moz-parse-options:format=bmp;bpp=32");
}
}
var applyIcons = function() {
if (cOS == 'Windows') {
Cu.import('resource://gre/modules/ctypes.jsm');
var user32 = ctypes.open('user32.dll');
/* http://msdn.microsoft.com/en-us/library/windows/desktop/ms644950%28v=vs.85%29.aspx
* LRESULT WINAPI SendMessage(
* __in HWND hWnd,
* __in UINT Msg,
* __in WPARAM wParam,
* __in LPARAM lParam
* );
*/
var SendMessage = user32.declare('SendMessageW', ctypes.winapi_abi, ctypes.uintptr_t,
ctypes.voidptr_t,
ctypes.unsigned_int,
ctypes.int32_t,
ctypes.voidptr_t
);
/* http://msdn.microsoft.com/en-us/library/windows/desktop/ms648045%28v=vs.85%29.aspx
* HANDLE WINAPI LoadImage(
* __in_opt_ HINSTANCE hinst,
* __in_ LPCTSTR lpszName,
* __in_ UINT uType,
* __in_ int cxDesired,
* __in_ int cyDesired,
* __in_ UINT fuLoad
* );
*/
var LoadImage = user32.declare('LoadImageA', ctypes.winapi_abi, ctypes.voidptr_t,
ctypes.voidptr_t,
ctypes.char.ptr,
ctypes.unsigned_int,
ctypes.int,
ctypes.int,
ctypes.unsigned_int
);
var IMAGE_BITMAP = 0;
var IMAGE_ICON = 1;
var LR_LOADFROMFILE = 16;
var DOMWindows = Services.wm.getEnumerator(null);
while (DOMWindows.hasMoreElements()) {
var aDOMWindow = DOMWindows.getNext();
var baseWindow = aDOMWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.treeOwner
.QueryInterface(Ci.nsIInterfaceRequestor)
.nsIBaseWindow;
var nativeHandle = baseWindow.nativeHandle;
var targetWindow_handle = ctypes.voidptr_t(ctypes.UInt64(nativeHandle));
console.log('aappplying now')
var hIconBig = LoadImage(targetWindow_handle, OS.Path.join(OS.Constants.Path.desktopDir, 'profilist32.' + osIconFileType), IMAGE_ICON, 32, 32, LR_LOADFROMFILE); //MUST BE A FILEPATH TO A ICO!!!
var hIconSmall = LoadImage(targetWindow_handle, OS.Path.join(OS.Constants.Path.desktopDir, 'profilist16.' + osIconFileType), IMAGE_ICON, 16, 16, LR_LOADFROMFILE); //MUST BE A FILEPATH TO A ICO!!!
var successSmall = SendMessage(targetWindow_handle, 0x0080 /** WM_SETICON **/ , 0 /** ICON_SMALL **/ , hIconSmall); //if it was success it will return 0? im not sure. on first time running it, and it was succesful it returns 0 for some reason
var successBig = SendMessage(targetWindow_handle, 0x0080 /** WM_SETICON **/ , 1 /** ICON_BIG **/ , hIconBig); //if it was success it will return 0? im not sure. on first time running it, and it was succesful it returns 0 for some reason
}
user32.close();
}
}
fp.open(fpCallback);
}
badgeIt();
Alright. This is actually quite reproducible, but only when using BMP icons, but not PNG icons.
Seems the icon encoder that Firefox ships is pretty bad/buggy indeed (for RGBA stuff). Well, actually it is the BMP encoder that the ICO encoder uses...
So since Belgium/Algeria (the game, football, not American) was mostly boring just now, I wrote my own icon encoder, which isn't too hard actually.
So here is my complete example code incl. icon encoder (just setting the 32x32 icon), but which lacks deposing of icons. But as a bonus, it shows how to set the icon via the WNDCLASS.
Cu.import('resource://gre/modules/ctypes.jsm');
Cu.import('resource://gre/modules/osfile.jsm');
let IMAGE_BITMAP = 0;
let IMAGE_ICON = 1;
let WM_SETICON = 128;
let GCLP_HICON = -14;
let user32 = ctypes.open('user32.dll');
let SendMessage = user32.declare(
'SendMessageW',
ctypes.winapi_abi,
ctypes.intptr_t,
ctypes.voidptr_t, // HWND
ctypes.uint32_t, // MSG
ctypes.uintptr_t, // WPARAM
ctypes.intptr_t // LPARAM
);
let CreateIconFromResourceEx = user32.declare(
'CreateIconFromResourceEx',
ctypes.winapi_abi,
ctypes.voidptr_t,
ctypes.uint8_t.ptr, // icon
ctypes.uint32_t, // size
ctypes.int32_t, // icon
ctypes.uint32_t, // dwVersion
ctypes.int, // dx
ctypes.int, // dy
ctypes.uint32_t // flags
);
let SetClassLongPtr = user32.declare(
ctypes.intptr_t.size == 8 ? 'SetClassLongPtrW' : 'SetClassLongW',
ctypes.winapi_abi,
ctypes.uintptr_t,
ctypes.voidptr_t, // HWND
ctypes.int, // index
ctypes.uintptr_t // value
);
let gdi32 = ctypes.open('gdi32.dll');
let DeleteObject = gdi32.declare(
'DeleteObject',
ctypes.winapi_abi,
ctypes.int,
ctypes.voidptr_t // Object
);
let setPerWindow = false;
let badges = [
'chrome://browser/skin/places/starred48.png',
'chrome://browser/skin/places/downloads.png',
'chrome://browser/skin/places/tag.png',
'chrome://browser/skin/places/livemark-item.png',
'chrome://browser/skin/places/query.png',
'chrome://browser/skin/pluginInstall-64.png',
'chrome://browser/skin/pluginInstall-16.png',
];
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
Task.spawn(function* setIcon() {
"use strict";
try {
let p = Promise.defer();
let img = new Image();
img.onload = () => p.resolve();
img.src = 'chrome://branding/content/icon32.png';
yield p.promise;
p = Promise.defer();
let badge = new Image();
badge.onload = () => p.resolve();
badge.src = badges[getRandomInt(0, badges.length - 1)];
console.log(badge.src);
yield p.promise;
let canvas = document.createElementNS(
'http://www.w3.org/1999/xhtml',
'canvas');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
let ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
let onethird = canvas.width / 3;
ctx.drawImage(
badge,
onethird,
onethird,
canvas.width - onethird,
canvas.height - onethird);
// Our own little ico encoder
// http://msdn.microsoft.com/en-us/library/ms997538.aspx
// Note: We would have been able to skip ICONDIR/ICONDIRENTRY,
// if we were to use CreateIconFromResourceEx only instead of also
// writing the icon to a file.
let data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
let XOR = data.length;
let AND = canvas.width * canvas.height / 8;
let size = 22 /* ICONDIR + ICONDIRENTRY */ + 40 /* BITMAPHEADER */ + XOR + AND;
let buffer = new ArrayBuffer(size);
// ICONDIR
let view = new DataView(buffer);
view.setUint16(2, 1, true); // type 1
view.setUint16(4, 1, true); // count;
// ICONDIRENTRY
view = new DataView(buffer, 6);
view.setUint8(0, canvas.width % 256);
view.setUint8(1, canvas.height % 256);
view.setUint16(4, 1, true); // Planes
view.setUint16(6, 32, true); // BPP
view.setUint32(8, 40 + XOR + AND, true); // data size
view.setUint32(12, 22, true); // data start
// BITMAPHEADER
view = new DataView(buffer, 22);
view.setUint32(0, 40, true); // BITMAPHEADER size
view.setInt32(4, canvas.width, true);
view.setInt32(8, canvas.height * 2, true);
view.setUint16(12, 1, true); // Planes
view.setUint16(14, 32, true); // BPP
view.setUint32(20, XOR + AND, true); // size of data
// Reorder RGBA -> BGRA
for (let i = 0; i < XOR; i += 4) {
let temp = data[i];
data[i] = data[i + 2];
data[i + 2] = temp;
}
let ico = new Uint8Array(buffer, 22 + 40);
let stride = canvas.width * 4;
// Write bottom to top
for (let i = 0; i < canvas.height; ++i) {
let su = data.subarray(XOR - i * stride, XOR - i * stride + stride);
ico.set(su, i * stride);
}
// Write the icon to inspect later. (We don't really need to write it at all)
let writePath = OS.Path.join(OS.Constants.Path.desktopDir, 'icon32.ico');
yield OS.File.writeAtomic(writePath, new Uint8Array(buffer), {
tmpPath: writePath + '.tmp'
});
// Cut off ICONDIR/ICONDIRENTRY for CreateIconFromResourceEx
buffer = buffer.slice(22);
let hicon = CreateIconFromResourceEx(
ctypes.uint8_t.ptr(buffer),
buffer.byteLength,
IMAGE_ICON,
0x30000,
0,
0,
0);
if (hicon.isNull()) {
throw new Error("Failed to load icon");
}
if (setPerWindow) {
let DOMWindows = Services.wm.getEnumerator(null);
while (DOMWindows.hasMoreElements()) {
let win = DOMWindows.getNext().QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIWebNavigation).
QueryInterface(Ci.nsIDocShellTreeItem).
treeOwner.QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIBaseWindow);
let handle = ctypes.voidptr_t(ctypes.UInt64(win.nativeHandle));
if (handle.isNull()) {
console.error("Failed to get window handle");
continue;
}
var lparam = ctypes.cast(hicon, ctypes.intptr_t);
var oldIcon = SendMessage(handle, WM_SETICON, 1, lparam);
if (ctypes.voidptr_t(oldIcon).isNull()) {
console.log("There was no old icon", oldIcon.toString());
}
else {
console.log("There was an old icon already", oldIcon.toString());
// In a perfect world, we should actually kill our old icons
// using DeleteObject...
}
}
}
else {
let win = Services.wm.getMostRecentWindow(null).
QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIWebNavigation).
QueryInterface(Ci.nsIDocShellTreeItem).
treeOwner.QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIBaseWindow);
let handle = ctypes.voidptr_t(ctypes.UInt64(win.nativeHandle));
if (handle.isNull()) {
throw new Error("Failed to get window handle");
}
let oldIcon = SetClassLongPtr(handle, GCLP_HICON, ctypes.cast(hicon, ctypes.uintptr_t));
if (ctypes.voidptr_t(oldIcon).isNull()) {
console.log("There was no old icon", oldIcon.toString());
}
else {
console.log("There was an old icon already", oldIcon.toString());
// In a perfect world, we should actually kill our old icons
// using DeleteObject...
}
}
console.log("done", badge.src);
}
catch (ex) {
console.error(ex);
}
});
PS: Here is a screenshot from the Task Switcher on XP:
Tested the code on Win7, the icon once applied is real real crappy. In image below, we see the icon in canvas is perfect. In the alt+tab menu the first icon is SUPER crappy, second icon is unbadged so perfect, and third and fifth icons are normal crappy.
Image is here: http://i.stack.imgur.com/47dIr.png
Edit:
Fixed the SUPER crappiness by changing this line for win7, it was 32, 32, i made it 256, 256, no clue why it fixed the SUPER crap:
var hIconBig = LoadImage(targetWindow_handle, OS.Path.join(OS.Constants.Path.desktopDir, 'profilist32.' + osIconFileType), IMAGE_ICON, 256, 256, LR_LOADFROMFILE);
before it was: var hIconBig = LoadImage(targetWindow_handle, OS.Path.join(OS.Constants.Path.desktopDir, 'profilist32.' + osIconFileType), IMAGE_ICON, 32, 32, LR_LOADFROMFILE);
However the usual crap, black rough edge remains.
I need to allow user to rotate bitmap features on map with OpenLayers.Control.ModifyFeature or another way as it work for Polygons or other geometry objects, except Point, but only for Point I can set "externalGraphic" with my bitmap. Example of ModifyFeature to rotation as I expected here: ModifyFeature example
When I add Vector with Point geometry and activate ModifyFeature there is no rotation tool showing - only drag-drop. I know what is a point, but I need to have tool for rotate bitmap features. It may be image on any another geometry object, but with custom image.
After long research I found an example in gis.stackexchange.com, and fix it.
Here is a code which works for me:
OpenLayers.Control.RotateGraphicFeature = OpenLayers.Class(OpenLayers.Control.ModifyFeature, {
rotateHandleStyle: null,
initialize: function(layer, options) {
OpenLayers.Control.ModifyFeature.prototype.initialize.apply(this, arguments);
this.mode = OpenLayers.Control.ModifyFeature.ROTATE; // This control can only be used to rotate the feature
this.geometryTypes = ['OpenLayers.Geometry.Point'] // This control can only be used to rotate point because the 'exteralGraphic' is a point style property
var init_style = OpenLayers.Util.extend({}, OpenLayers.Feature.Vector.style.select);
this.rotateHandleStyle = OpenLayers.Util.extend(init_style, {
externalGraphic: "./static/resources/images/cross.png",
graphicWidth: 12,
graphicHeight: 12,
fillOpacity: 1
});
},
resetVertices: function() {
// You need to set yours renderIntent or use "vertex"
if (this.feature && this.feature.renderIntent == "vertex") {
var vertex = this.feature;
this.feature = this.backup_feature;
this.layer.destroyFeatures([this.radiusHandle], {silent: true});
this.collectRadiusHandle();
return;
}
if (this.dragControl.feature) {
this.dragControl.outFeature(this.dragControl.feature);
}
if (this.vertices.length > 0) {
this.layer.removeFeatures(this.vertices, {silent: true});
this.vertices = [];
}
if (this.virtualVertices.length > 0) {
this.layer.removeFeatures(this.virtualVertices, {silent: true});
this.virtualVertices = [];
}
if (this.dragHandle) {
this.layer.destroyFeatures([this.dragHandle], {silent: true});
this.dragHandle = null;
}
if (this.radiusHandle) {
this.layer.destroyFeatures([this.radiusHandle], {silent: true});
this.radiusHandle = null;
}
if (this.feature && this.feature.geometry &&
this.feature.geometry.CLASS_NAME != "OpenLayers.Geometry.Point") {
if ((this.mode & OpenLayers.Control.ModifyFeature.DRAG)) {
this.collectDragHandle();
}
if ((this.mode & (OpenLayers.Control.ModifyFeature.ROTATE |
OpenLayers.Control.ModifyFeature.RESIZE))) {
this.collectRadiusHandle();
}
if (this.mode & OpenLayers.Control.ModifyFeature.RESHAPE) {
if (!(this.mode & OpenLayers.Control.ModifyFeature.RESIZE)) {
this.collectVertices();
}
}
}
this.collectRadiusHandle();
},
collectRadiusHandle: function() {
var scope = this,
feature = this.feature,
geometry = this.feature.geometry || this.backup_feature.geometry,
centroid = geometry.getCentroid().transform(this.WGS84_google_mercator, this.WGS84),
lon = centroid.x, lat = centroid.y;
if (this.feature.geometry) {
this.backup_feature = this.feature;
} else {
this.feature.geometry = this.backup_feature.geometry;
}
var originGeometry = new OpenLayers.Geometry.Point(lon, lat);
// radius geometry position.
var pixel_dis_x = 10,
pixel_dis_y = -10;
var rotationFeatureGeometry = new OpenLayers.Geometry.Point(lon+pixel_dis_x, lat+pixel_dis_y);
var rotationFeature = new OpenLayers.Feature.Vector(rotationFeatureGeometry, null, this.rotateHandleStyle);
var resize = (this.mode & OpenLayers.Control.ModifyFeature.RESIZE);
var reshape = (this.mode & OpenLayers.Control.ModifyFeature.RESHAPE);
var rotate = (this.mode & OpenLayers.Control.ModifyFeature.ROTATE);
rotationFeatureGeometry.move = function(x, y) {
OpenLayers.Geometry.Point.prototype.move.call(this, x, y);
var dx1 = this.x - originGeometry.x;
var dy1 = this.y - originGeometry.y;
var dx0 = dx1 - x;
var dy0 = dy1 - y;
if (rotate) {
var a0 = Math.atan2(dy0, dx0);
var a1 = Math.atan2(dy1, dx1);
var angle = a1 - a0;
angle *= 180 / Math.PI;
var old_angle = feature.attributes.angle;
var new_angle = old_angle - angle;
feature.attributes.angle = new_angle;
// redraw the feature
scope.feature.layer.redraw.call(scope.feature.layer);
}
};
rotationFeature._sketch = true;
this.radiusHandle = rotationFeature;
this.radiusHandle.renderIntent = this.vertexRenderIntent;
this.layer.addFeatures([this.radiusHandle], {silent: true});
},
CLASS_NAME: "OpenLayers.Control.RotateGraphicFeature"
});
Style of your Vector may be as:
new OpenLayers.StyleMap({
"default": new OpenLayers.Style({
externalGraphic: "link/to/icon",
graphicHeight: "32px",
graphicWidth: "25px",
fillOpacity: 1,
rotation: "${angle}",
graphicZIndex: 1
})
})
UPD: I fixed it for OpenLayers 2.13.1
OpenLayers.Control.RotateGraphicFeature = OpenLayers.Class(OpenLayers.Control.ModifyFeature, {
rotateHandleStyle: null,
initialize: function (layer, options) {
OpenLayers.Control.ModifyFeature.prototype.initialize.apply(this, arguments);
this.mode = OpenLayers.Control.ModifyFeature.ROTATE; // This control can only be used to rotate the feature
this.geometryTypes = ['OpenLayers.Geometry.Point'] // This control can only be used to rotate point because the 'exteralGraphic' is a point style property
var init_style = OpenLayers.Util.extend({}, OpenLayers.Feature.Vector.style.select);
this.rotateHandleStyle = OpenLayers.Util.extend(init_style, {
externalGraphic: "./static/resources/images/cross.png",
graphicWidth: 12,
graphicHeight: 12,
fillOpacity: 1
});
},
resetVertices: function () {
// You need to set yours renderIntent or use "vertex"
if (this.feature && this.feature.renderIntent == "vertex") {
var vertex = this.feature;
this.feature = this.backup_feature;
if (this.dragControl.feature) {
this.dragControl.outFeature(this.dragControl.feature);
}
this.layer.destroyFeatures([this.radiusHandle], {silent: true});
delete this.radiusHandle;
this.collectRadiusHandle();
return;
}
if (this.vertices.length > 0) {
this.layer.removeFeatures(this.vertices, {silent: true});
this.vertices = [];
}
if (this.virtualVertices.length > 0) {
this.layer.removeFeatures(this.virtualVertices, {silent: true});
this.virtualVertices = [];
}
if (this.dragHandle) {
this.layer.destroyFeatures([this.dragHandle], {silent: true});
this.dragHandle = null;
}
if (this.radiusHandle) {
this.layer.destroyFeatures([this.radiusHandle], {silent: true});
this.radiusHandle = null;
}
if (this.feature && this.feature.geometry &&
this.feature.geometry.CLASS_NAME != "OpenLayers.Geometry.Point") {
if ((this.mode & OpenLayers.Control.ModifyFeature.DRAG)) {
this.collectDragHandle();
}
if ((this.mode & (OpenLayers.Control.ModifyFeature.ROTATE |
OpenLayers.Control.ModifyFeature.RESIZE))) {
this.collectRadiusHandle();
}
if (this.mode & OpenLayers.Control.ModifyFeature.RESHAPE) {
if (!(this.mode & OpenLayers.Control.ModifyFeature.RESIZE)) {
this.collectVertices();
}
}
}
this.collectRadiusHandle();
},
collectRadiusHandle: function () {
var scope = this,
feature = this.feature,
data = feature.attributes,
geometry = this.feature.geometry || this.backup_feature.geometry,
center = this.feature.geometry.bounds.getCenterLonLat();
centroid = geometry.getCentroid().transform(this.WGS84_google_mercator, this.WGS84),
lon = centroid.x, lat = centroid.y;
if (data.type && Tms.settings.roadObjectTypeSettings[data.type].NoAzimuth) {
return;
}
if (this.feature.geometry) {
this.backup_feature = this.feature;
} else {
this.feature.geometry = this.backup_feature.geometry;
}
var originGeometry = new OpenLayers.Geometry.Point(lon, lat);
var center_px = this.map.getPixelFromLonLat(center);
// you can change this two values to get best radius geometry position.
var pixel_dis_x = 20,
pixel_dis_y = 20;
var radius_px = center_px.add(pixel_dis_x, pixel_dis_y);
var rotation_lonlat = this.map.getLonLatFromPixel(radius_px);
var rotationFeatureGeometry = new OpenLayers.Geometry.Point(
rotation_lonlat.lon, rotation_lonlat.lat
);
var rotationFeature = new OpenLayers.Feature.Vector(rotationFeatureGeometry, null, this.rotateHandleStyle);
var resize = (this.mode & OpenLayers.Control.ModifyFeature.RESIZE);
var reshape = (this.mode & OpenLayers.Control.ModifyFeature.RESHAPE);
var rotate = (this.mode & OpenLayers.Control.ModifyFeature.ROTATE);
rotationFeatureGeometry.move = function(x, y) {
OpenLayers.Geometry.Point.prototype.move.call(this, x, y);
var dx1 = this.x - originGeometry.x;
var dy1 = this.y - originGeometry.y;
var dx0 = dx1 - x;
var dy0 = dy1 - y;
if (rotate) {
var a0 = Math.atan2(dy0, dx0);
var a1 = Math.atan2(dy1, dx1);
var angle = a1 - a0;
angle *= 180 / Math.PI;
var old_angle = feature.attributes.angle;
var new_angle = old_angle - angle;
feature.attributes.angle = new_angle;
// redraw the feature
scope.feature.layer.redraw.call(scope.feature.layer);
}
};
rotationFeature._sketch = true;
this.radiusHandle = rotationFeature;
this.radiusHandle.renderIntent = this.vertexRenderIntent;
this.layer.addFeatures([this.radiusHandle], {silent: true});
},
CLASS_NAME: "OpenLayers.Control.RotateGraphicFeature"
});