I designed a web app with html5 canvas. To export an image, the code will be below:
var img = canvas.toDataURL("image/png");
Is there any way to export a 2x image?
It is for hdpi display like apple retina display.
Yes there are a few ways but every time you stretch a non vector image you will get some pixel distortion. However if its only two times the size you could get away with it using nearest neighbor. The below example shows two different methods, one is just stretching the image, the other uses nearest neighbor with a zoom factor of two.
Live Demo
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
canvas2 = document.getElementById("canvas2"),
ctx2 = canvas2.getContext("2d"),
tempCtx = document.createElement('canvas').getContext('2d'),
img = document.getElementById("testimg"),
zoom = 2;
tempCtx.drawImage(img, 0, 0);
var imgData = tempCtx.getImageData(0, 0, img.width, img.height).data;
canvas.width = img.width * zoom;
canvas.height = img.height * zoom;;
// nearest neighbor
for (var x = 0; x < img.width; ++x) {
for (var y = 0; y < img.height; ++y) {
var i = (y * img.width + x) * 4;
var r = imgData[i];
var g = imgData[i + 1];
var b = imgData[i + 2];
var a = imgData[i + 3];
ctx.fillStyle = "rgba(" + r + "," + g + "," + b + "," + (a / 255) + ")";
ctx.fillRect(x * zoom, y * zoom, zoom, zoom);
}
}
// stretched
ctx2.drawImage(img, 0, 0, 140, 140);
#phrogz has a great example of this here as well, showing a few different ways you can accomplish image re-sizing.
Related
I'm using D3.js to create a globe. I have a working SVG wife-frame version, and I'm also trying to create a more detailed textured one, a two-mode thing.
The image I'm using from an API is square:
Which doesn't really work out well when projected to orthographic, it's a lot more "squished" towards the equator than it should be:
Not doing anything particularly special:
const dx = 2048;
const dy = 2048;
const width = 2048;
const height = 2048;
let sourceData = mapImage.getImageData(0, 0, dx, dy).data,
target = ctx.createImageData(width, height),
targetData = target.data;
for (let y = 0, i = -1; y < height; ++y) {
for (let x = 0; x < width; ++x) {
let p = projection.invert([x, y]);
if (p[0] > 180 || p[0] < -180 || p[1] > 90 || p[1] < -90) {
i += 4;
continue;
}
let q = ((90 - p[1]) / 180 * dy | 0) * dx + ((180 + p[0]) / 360 * dx | 0) << 2;
targetData[++i] = sourceData[q];
targetData[++i] = sourceData[++q];
targetData[++i] = sourceData[++q];
targetData[++i] = 255;
}
}
ctx.clearRect(0, 0, width, height);
ctx.putImageData(target, 0, 0);
I'm wondering if there's a straightforward way to make the additional adjustment for the stretching of the map image?
(Bonus points if you can also point me to why the space around the globe is not transparent? But that's not the main question here.)
I'm doing Spritefonts and currently implemented tint for it on WebGL!
But on canvas2d i tried to do it via ctx.globalCompositeOperation but it shows following
As you see, Black pixels are also filled...
Here is my code...
var size = 32;
var x = 200;
var y = 200;
var spacing = 0;
for (var i = 0; i < txt.length; i++) {
var q = fonts[0].info[txt[i]];
ctx.save();
if (q) ctx.drawImage(fonts[0].src, q.x, q.y, q.w, q.h, x + (spacing || 0) + (i * size), y, size, size);
ctx.globalCompositeOperation = "source-in";
ctx.fillStyle = "green";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.restore();
}
When trying with "darken" mode instead, It fills correctly but also it fills background (Which i don't want this...)
I also tried with ctx.getImageData() and ctx.putImageData() but letters not shown
var size = 32;
var x = 200;
var y = 200;
var spacing = 0;
for (var i = 0; i < txt.length; i++) {
var q = fonts[0].info[txt[i]];
if (q) {
ctx.drawImage(fonts[0].src, q.x, q.y, q.w, q.h, x + (spacing || 0) + (i * size), y, size, size);
f = ctx.getImageData(x + (spacing || 0) + (i * size), y, size, size);
for (var i = 0; i < f.data.length; i += 4) {
f.data[i + 0] = 100;
f.data[i + 1] = 100;
f.data[i + 2] = 255;
f.data[i + 3] = 255;
}
ctx.putImageData(f, x + (spacing || 0) + (i * size), y, 0, 0, size, size);
}
}
The image i'm using is from here
Fixed by using "lighten" mode for black pixels with filling background, Then applied "darken" mode instead of "source-in" and all done!
var size = 32;
var x = 200;
var y = 200;
var spacing = 0;
for (var i = 0; i < txt.length; i++) {
var q = fonts[0].info[txt[i]];
ctx.save();
ctx.globalCompositeOperation = "lighten";
ctx.fillStyle = ctx.canvas.style.backgroundColor;
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
if (q) ctx.drawImage(fonts[0].src, q.x, q.y, q.w, q.h, x + (spacing || 0) + (i * size), y, size, size);
ctx.globalCompositeOperation = "darken";
ctx.fillStyle = "green";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.restore();
}
This is better way i found:
Create canvas with dimensions that complies with spritefont image dimensions
Save context state in the created canvas
Set fillStyle of the created canvas context with spritefont text color (Tint)
Set globalAlpha of created canvas context to opacity
Fill created canvas background with spritefont text color (Tint)
Apply "destination-atop" composite mode in created canvas context
Reset globalAlpha of created canvas context to 1 (Default)
Draw spritefont image onto created canvas
Restore context state in created canvas
Then, Let default canvas context (Not created one) draw characters from spritefont image, So we let it draw part of canvas we created (Note that spritefont image fills all created canvas)
Done!
var size = 32;
var x = 200;
var y = 200;
var spacing = 0;
var opacity = 0.8;
var color = "green";
for (var i = 0; i < txt.length; i++) {
var q = fonts[0].info[txt[i]];
var c = document.createElement("canvas").getContext("2d");
c.canvas.width = fonts[0].src.width;
c.canvas.height = fonts[0].src.height;
c.save();
c.fillStyle = color;
c.globalAlpha = opacity || 0.8;
c.fillRect(0, 0, c.canvas.width, c.canvas.height);
c.globalCompositeOperation = "destination-atop";
c.globalAlpha = 1;
c.drawImage(fonts[0].src, 0, 0);
c.restore();
if (q) ctx.drawImage(c.canvas, q.x, q.y, q.w, q.h, x + (i * (size + spacing)), y, size, size);
}
I'm generating image programmatically inside canvas.
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
// here I have some code in loop setting individual pixels
// ...
//
// save image to variable
var dataURL = canvas.toDataURL();
How can I rotate created image by 90 degrees?
EDIT:
This is not duplicate because I don't draw image, it is never visible. I only want to generate it, rotate it and save to variable.
EDIT2:
I'm trying to rotate it with this code:
ctx.translate(canvas.width / 2, canvas.height / 2)
ctx.rotate(90 * Math.PI / 180)
But it doesn't work
EDIT3:
This is more complex example of my code:
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
canvas.setPixel = function (x, y, color) {
ctx.fillStyle = color;
ctx.fillRect(x, y, 1, 1);
}
for (var i in data) {
for (var j in data[i]) {
switch (data[i][j]) {
case 1:
var color = '#ffff00',
type = 'w'
break
case 3:
var rgb = (256 - parseInt(pixels[i][j]) - minus.grass).toString(16),
color = '#00' + rgb + '00',
type = 'g'
break
case 4:
var rgb = (256 - parseInt(pixels[i][j]) - minus.hills).toString(16),
color = '#' + rgb + rgb + '00',
type = 'h'
break
case 5:
var rgb = (parseInt(pixels[i][j]) + minus.mountains).toString(16),
color = '#' + rgb + rgb + rgb,
type = 'm'
break
case 6:
var rgb = (parseInt(pixels[i][j]) + minus.snow).toString(16),
color = '#' + rgb + rgb + rgb,
type = 'm'
break
}
if (i % fieldSize == 0 && j % fieldSize == 0) {
if (notSet(fields[y])) {
fields[y] = []
}
fields[y][x] = type
x++
}
canvas.setPixel(i, j, color)
}
if (i % fieldSize == 0) {
x = 0
y++
}
}
ctx.translate(canvas.width / 2, canvas.height / 2)
ctx.rotate(90 * Math.PI / 180)
var token = {
type: 'save',
map: canvas.toDataURL('image/png')
}
ws.send(JSON.stringify(token))
To rotate image by 90 degrees I had to put
ctx.translate(0, canvas.height)
ctx.rotate(270 * Math.PI / 180)
before
for (var i in data) {
for (var j in data[i]) {
switch (data[i][j]) {
// ... drawing pixels
}
}
}
I have trying to render an image in the browser which is built like this:
A bunch of rectangles are each filled with a radial gradient (ideally Gaussian, but can be approximated with a few stopping points
Each rectangle is rotated and translated before being deposited on a drawing area
The image is flattened by summing all the intensities of the rectangles (and cropping to the drawing area's dimensions )
The intensity is rescaled so that the highest intensity is 255 and the lowest 0 (ideally I can apply some sort of gamma correction too)
Finally an image is drawn where the color of each pixel is taken from a palette of 256 colors.
The reason I cannot do this easily with a canvas object is that I need to be working in floating points or I'll lose precision. I do not know in advance what the maximum intensity and minimum intensity will be, so I cannot merely draw transparent rectangles and hope for the best.
Is there a way to do this in webgl? If so, how would I go about it?
You can use the regular canvas to perform this task :
1) check min/max of your rects, so you can build a mapping function double -> [0-255] out of that range.
2) draw the rects in 'lighter' mode == add the component values.
3) you might have a saturation when several rects overlaps : if so, double the mapping range and go to 2).
Now if you don't have saturation just adjust the range to use the full [0-255] range of the canvas, and you're done.
Since this algorithm makes use of getImageData, it might not reach 60 fps on all browsers/devices. But more than 10fps on desktop/Chrome seems perfectly possible.
Hopefully the code below will clarify my description :
//noprotect
// boilerplate
var cv = document.getElementById('cv');
var ctx = cv.getContext('2d');
// rectangle collection
var rectCount = 30;
var rects = buildRandRects(rectCount);
iterateToMax();
// --------------------------------------------
function iterateToMax() {
var limit = 10; // loop protection
// initialize min/max mapping based on rects min/max
updateMapping(rects);
//
while (true) {
// draw the scene using current mapping
drawScene();
// get the max int value from the canvas
var max = getMax();
if (max == 255) {
// saturation ?? double the min-max interval
globalMax = globalMin + 2 * (globalMax - globalMin);
} else {
// no sauration ? Just adjust the min-max interval
globalMax = globalMin + (max / 255) * (globalMax - globalMin);
drawScene();
return;
}
limit--;
if (limit <= 0) return;
}
}
// --------------------------------------------
// --------------------------------------------
// Oriented rectangle Class.
function Rect(x, y, w, h, rotation, min, max) {
this.min = min;
this.max = max;
this.draw = function () {
ctx.save();
ctx.fillStyle = createRadialGradient(min, max);
ctx.translate(x, y);
ctx.rotate(rotation);
ctx.scale(w, h);
ctx.fillRect(-1, -1, 2, 2);
ctx.restore();
};
var that = this;
function createRadialGradient(min, max) {
var gd = ctx.createRadialGradient(0, 0, 0, 0, 0, 1);
var start = map(that.min);
var end = map(that.max);
gd.addColorStop(0, 'rgb(' + start + ',' + start + ',' + start + ')');
gd.addColorStop(1, 'rgb(' + end + ',' + end + ',' + end + ')');
return gd;
}
}
// Mapping : float value -> 0-255 value
var globalMin = 0;
var globalMax = 0;
function map(value) {
return 0 | (255 * (value - globalMin) / (globalMax - globalMin));
}
// create initial mapping
function updateMapping(rects) {
globalMin = rects[0].min;
globalMax = rects[0].max;
for (var i = 1; i < rects.length; i++) {
var thisRect = rects[i];
if (thisRect.min < globalMin) globalMin = thisRect.min;
if (thisRect.max > globalMax) globalMax = thisRect.max;
}
}
// Random rect collection
function buildRandRects(rectCount) {
var rects = [];
for (var i = 0; i < rectCount; i++) {
var thisMin = Math.random() * 1000;
var newRect = new Rect(Math.random() * 400, Math.random() * 400, 10 + Math.random() * 50, 10 + Math.random() * 50, Math.random() * 2 * Math.PI, thisMin, thisMin + Math.random() * 1000);
rects.push(newRect);
}
return rects;
}
// draw all rects in 'lighter' mode (=sum values)
function drawScene() {
ctx.save();
ctx.globalCompositeOperation = 'source-over';
ctx.clearRect(0, 0, cv.width, cv.height);
ctx.globalCompositeOperation = 'lighter';
for (var i = 0; i < rectCount; i++) {
var thisRect = rects[i];
thisRect.draw();
}
ctx.restore();
}
// get maximum value for r for this canvas
// ( == max r, g, b value for a gray-only drawing. )
function getMax() {
var data = ctx.getImageData(0, 0, cv.width, cv.height).data;
var max = 0;
for (var i = 0; i < data.length; i += 4) {
if (data[i] > max) max = data[i];
if (max == 255) return 255;
}
return max;
}
<canvas id='cv' width = 400 height = 400></canvas>
I load a model with vertex and vertex nromal,
for (var i = 0, vindex = 0; i < triangle.length; i++, vindex += 3) {
x = parseFloat(triangle[i].attributes.getNamedItem('x1').value);
y = parseFloat(triangle[i].attributes.getNamedItem('y1').value);
z = parseFloat(triangle[i].attributes.getNamedItem('z1').value);
this.geometry.vertices.push(new THREE.Vector3(x * scale + this.translateVector.x, y * scale + this.translateVector.y, z * scale + this.translateVector.z));
x = parseFloat(triangle[i].attributes.getNamedItem('x2').value);
y = parseFloat(triangle[i].attributes.getNamedItem('y2').value);
z = parseFloat(triangle[i].attributes.getNamedItem('z2').value);
this.geometry.vertices.push(new THREE.Vector3(x * scale + this.translateVector.x, y * scale + this.translateVector.y, z * scale + this.translateVector.z));
x = parseFloat(triangle[i].attributes.getNamedItem('x3').value);
y = parseFloat(triangle[i].attributes.getNamedItem('y3').value);
z = parseFloat(triangle[i].attributes.getNamedItem('z3').value);
this.geometry.vertices.push(new THREE.Vector3(x * scale + this.translateVector.x, y * scale + this.translateVector.y, z * scale + this.translateVector.z));
var face = new THREE.Face3(vindex, vindex + 1, vindex + 2);
face.color.setHex(this.faceColor || this.defaultcolor);
face.vertexNormals = [];
nx = parseFloat(triangle[i].attributes.getNamedItem('nx1').value);
ny = parseFloat(triangle[i].attributes.getNamedItem('ny1').value);
nz = parseFloat(triangle[i].attributes.getNamedItem('nz1').value);
face.vertexNormals.push(new THREE.Vector3(-nx, -ny, -nz));
nx1 = parseFloat(triangle[i].attributes.getNamedItem('nx2').value);
ny1 = parseFloat(triangle[i].attributes.getNamedItem('ny2').value);
nz1 = parseFloat(triangle[i].attributes.getNamedItem('nz2').value);
face.vertexNormals.push(new THREE.Vector3(-nx1, -ny1, -nz1));
nx2 = parseFloat(triangle[i].attributes.getNamedItem('nx3').value);
ny2 = parseFloat(triangle[i].attributes.getNamedItem('ny3').value);
nz2 = parseFloat(triangle[i].attributes.getNamedItem('nz3').value);
face.vertexNormals.push(new THREE.Vector3(-nx2, -ny2, -nz2));
face.normal.set((nx + nx1 + nx2) / 3, (ny + ny1 + ny2) / 3,(nz + nz1 + nz2) / 3);
this.geometry.faces.push(face);
}
this.material = new THREE.MeshBasicMaterial({ vertexColors: THREE.FaceColors, overdraw: true , opacity: 1, transparent: 0 });
this.mesh = new THREE.Mesh(this.geometry, this.material);
this.mesh.name = this.id;
this.mesh.updateMatrix();
this.mesh.matrixAutoUpdate = false;
scene.add(this.mesh);
the house below, front face is invisible, so front wall and left wall is invisible, we can see through the inside of house, but I want it to show all walls and not see through, could anyone help me?
after I change to Lambert material it still show house inside, I've tried, cw,ccw, or invert index of vertex, invert normal. could any body help?
it is possible there is something wrong with the face UV's. try making the material applied doublesided.
seems find the answer.
that's because part of the house model position.z < 0, and camera's near < 0, maybe three.js z-buffer clear negative = 0, z-buffer determines the sheltery relation.