I am trying to write a javascript function to resize an image based on a given area (or in my case (somewhat inaccurate) 'average dimension' since that's easier to think in terms of. Rather than feeding in maximum height and width, I want to feed in maximum area so that long or narrow images will appear visually to be roughly the same size.
I'm getting really caught on the math aspect of it, though... just how to logic it, as I haven't done much math of late.
Basically, given an aspect ratio I want to determine the maximum size within an area.
Something like this:
function resizeImgByArea(img, avgDimension){
var w = $(img).width();
var h = $(img).height();
var ratio = w/h;
var area = avgDimension * avgDimension;
var targetHeight //something involving ratio and area
var targetWidth //something involving ratio and area
$(img).width(targetWidth);
$(img).height(targetHeight);
}
Not sure if this is on topic here, but I'm not able to brain it.
Sounds like you want to constrain the thumbnail's pixels to be as close as possible to the average area as all the other thumbnails, right?
So basically, given the h/w of the original image, and a target area A:
h * w = original image's pixel size (let's got with 640x480 = 307,200 pixels)
A = maximum number of pixels allowed (let's go for 100x100 = 10,000 pixels)
307,200 / 10,000 = 30x reduction
original aspect ratio = 640 / 480 = 1.3333 : 1
To calculate the new thumbnail's x/y size:
newX * newY = 10000
newX = newY * 1.333
(newY * 1.333) * newY = 10000
newY^2 * 1.333 = 10000
newY^2 = 10000 / 1.333
newY^2 = 7502
newY = 86.6 -> 87
newX = 87 * 1.333 = 115.97 -> 116
116 x 87 = 10,092 pixels
if we'd rounded down on the thumbnail sizes, we'd get 86x114 = 9,804 pixels
so... to convert your 640x480 image to a standard 10,000-ish pixel size, you need a new image size of 86-87 height and 114-116 width.
Are you looking for something like:
function resizeImgByArea(img, avgDimension) {
var w = $(img).width();
var h = $(img).height();
var maxWidth = avgDimension;
var maxHeight = avgDimension;
var divisor;
var targetWidth = w;
var targetHeight = h;
if (w > maxWidth || h > maxHeight) {
// Set the divisor to whatever will make the new image fit the dimensions given
if((w - maxWidth) > (h - maxHeight)) {
divisor = w / maxWidth;
}
else {
divisor = h / maxHeight;
}
targetWidth = w / divisor;
targetHeight = h / divisor;
}
$(img).width(targetWidth);
$(img).height(targetHeight);
}
It isn't that hard.
maxPix = average^2
maxPix = x * h + x * w
average^2 = x*h + x*w //: x
average^2/x = h+w
inverse and multiply with average^2
x = average^2 / (h+w)
then multiply h and w with x to get the new dimensions
Here is the function I came up with that's simpler than some mentioned and does what I need. It constrains to a set maxWidth, but not height because of the particular requirements I was using.. it would probly be appropriate to throw on a maxHeight as well as well as some cleanup, but it gets 'er done.
function resizeImgByArea(imgId, avgDimension){
var node, w, h, oldArea, oldAvgDimension, multiplicator, targetHeight, targetWidth, defAvgDimension;
node = $('#' + imgId);
node.css('width', '').css('height', '');
var maxW = $('#' + imgId).css('maxWidth');
if (maxW){
defAvgDimension = maxW;
} else {
defAvgDimension = 200;
}
avgDimension = (typeof avgDimension == "undefined")?'defAvgDimension':avgDimension;
w = node.width();
h = node.height();
oldArea = w*h;
oldAvgDimension = Math.sqrt(oldArea);
if (oldAvgDimension > avgDimension){
multiplicator = avgDimension / oldAvgDimension;
targetHeight = h * multiplicator;
targetWidth = w * multiplicator;
node.width(targetWidth);
node.height(targetHeight);
}
}
function fitImageInArea(img, area) {
var r;
if (img.width/img.height >= area.width/area.height) {
r = area.width / img.width;
img.width = area.width;
img.height = img.height*r;
} else {
r = area.height / img.height;
img.height = area.height;
img.width = img.width*r;
}
return img;
}
Give an image or anything with width and height properties as an argument. area argument assume width and height properties too.
Related
Is there a way of calculating / estimating what the image size would be depending on the screen resolution before the image has been rendered? I need more logical help rather than code to be honest. What do I need to calculate?
Image size: 800px * 450px
Window size: 424px * 728px
The image works out to be 424px * 239px. I need to calculate this in code so I can adjust positions of other elements following after (absolute / fixed elements).
What I have done so far is;
var ratio1 = (this.retrievedNewsArticle.featuredImage.width / this.retrievedNewsArticle.featuredImage.height);
var ratio2 = ($(window).innerWidth() / this.retrievedNewsArticle.featuredImage.width);
// Ratio 1 = 424
// Ratio 2 = 0.53
So what's next?
It sounds like you already know the image's size, and it sounds like you want the image to be the full width of the window, and just need to know how to determine the height of the image. If so, the target height is the image height times the windows width divided by the image width:
var renderedWidth = imageWidth;
var renderedHeight = imageHeight * (windowWidth / imageWidth);
That maintains the aspect ratio.
That assumes the image is always wider than the screen. Let's remove that assumption.
If you want the image to stretch to fill:
var renderedWidth, renderedHeight;
if (windowWidth >= imageWidth) {
renderedWidth = imageWidth * (windowWidth / imageWidth);
} else {
renderedWidth = imageWidth;
}
renderedHeight = imageHeight * (windowWidth / renderedWidth);
If you don't want the image to stretch to fill:
var renderedWidth, renderedHeight;
if (windowWidth >= imageWidth) {
renderedWidth = imageWidth;
renderedHeight = imageHeight;
} else {
renderedWidth = imageWidth;
renderedHeight = imageHeight * (windowWidth / imageWidth);
}
I have a bunch of images I am parsing and I have access to their url, height, and width.
I want to place these images in a photo grid that has three different objects of set height and width. I am trying to come up with a good way to place each image in each "class" of item based off its dimensions and which one it would "fit" best with so it is not stretched.
For example:
var imageSource = //source of image;
var imageHeight = //height of image;
var imageWidth = //width of image;
var imageClass = bestClass(imageHeight, imageWidth);
function bestClass(height, width){
var classes = ["250,500", "500,500", "300,400"];
//do some magic to determine which class is the best dimensions for this image in a photo grid.
}
I wanted to post this question to see if there was any magic built in that helped me out here before I dug into finding the mean difference between height, width and comparing it to the nearest "bin" or whatever...
As you have answers that technically achieve what you are after I thought I'd put it to you differently:
Is squeezing a 300x500 image into a 250x500 space really acceptable? There will still be distortions.
The ideal answer would be to restructure your data to put some constraints on image dimension ratio, either when images are inserted into the system or run some image processing to crop images to the correct ratio. Note that background-size: cover; may help here, and place the image into the background (background-size only works on background images, not <img /> elements). Even better than constraining resolution would be to constrain size to the exact sizes you want.
Ideally you'd probably want to change your presentation layer to work with your data (which is arbitrarily sized), but, if that is not an option, you have to try and manipulate your data to match how you want to present. Stretched or squeezed images are never going to look good.
Using the height/width ratio:
var func = function bestClass(height, width){
var classes = [{ Width: 250, Height: 500} , { Width: 500, Height: 500}, { Width: 300, Height: 400}];
var minDist = 1;
var ratio = height / width;
var index = 0;
for(var i = 0; i < 3; i++) {
var dist = Math.abs((classes[i].Height / classes[i].Width) - ratio);
if (dist < minDist) {
minDist = dist;
index = i;
}
}
return classes[index];
}
var result = func(100,100);
You can try using the ratio height/width.
var ratio = (imageHeight/imageWidth);
if ( Math.abs(ratio - 250/500) <= Math.abs(ratio - 500/500) && Math.abs(ratio - 250/500) <= Math.abs(ratio - 300/400){
console.log("best is 250/500")
}
else if ( Math.abs(ratio - 500/500) <= Math.abs(ratio - 250/500) && Math.abs(ratio - 500/500) <= Math.abs(ratio - 300/400){
console.log("best is 500/500")
}
else{console.log("best is 300/400")}
Update: If you dont want to be any distortions and just find the perfect size to fit the images in these classes use the image ratio and the differece between the class ratio like this:
var ratio = (imageHeight/imageWidth);
if ( Math.abs(ratio - 250/500) <= Math.abs(ratio - 500/500) && Math.abs(ratio - 250/500) <= Math.abs(ratio - 300/400){
var dif = ratio-0.5;
if(dif<=0){
var newImageWidth = 500;
var newImageHeight = 500*ratio
}
else{
var newImageWidth = 500 - (500*dif);
var newImageHeight = 250;
}
}
else if ( Math.abs(ratio - 500/500) <= Math.abs(ratio - 250/500) && Math.abs(ratio - 500/500) <= Math.abs(ratio - 300/400){
console.log("best is 500/500");
var dif = ratio-1;
if(dif<=0){
var newImageWidth = 500;
var newImageHeight = 500*ratio
}
else{
var newImageWidth = 500 - (500*dif);
var newImageHeight = 500;
}
}
else{console.log("best is 300/400")
var dif = ratio-0.75;
if(dif<=0){
var newImageWidth = 400;
var newImageHeight = 400*ratio
}
else{
var newImageWidth = 400 - (400*dif);
var newImageHeight = 300;
}
}
I'm trying to make a square appear at random positions of the screen. I have set it's position property to be absolute and in javascript i'm running a random number between 0 to 100, this will then be assigned as a percentage to top and left property. however if the random number was ~100 or a bit less the square will appear out of the screen. How do I fix this problem?
var shape1 = document.getElementById("shape1");
//creating random number to 100
function randomNum() {
var r = Math.random();
var y = (r * (100 - 0 + 1)) + 0;
var x = Math.floor(y);
console.log(x);
return x;
}
//reappear at random position
function reappear(object) {
object.style.left = randomNum().toString() + "%";
object.style.top = randomNum().toString() + "%";
object.style.display = "block";
}
reappear(shape1);
code: https://jsfiddle.net/y3m4ygow/1/
You can call the getBoundingClientRect method (MDN reference) on the object and check to see if its bottom property is bigger than window.innerHeight (means it's falling outside the window height), or if its right property is bigger than window.innerWidth (means it's falling outside the window width), and if so, call the reappear function again:
function reappear(object) {
object.style.left = randomNum().toString() + "%";
object.style.top = randomNum().toString() + "%";
object.style.display = "block";
var rect = object.getBoundingClientRect();
if(rect.right > window.innerWidth || rect.bottom > window.innerHeight)
reappear(object);
}
Fiddle update: https://jsfiddle.net/y3m4ygow/2/
As you can see what's happening here is sometimes the object falling out of the document because (the width or height of it + the randomized percentage) is more than document width or height.
For example, say that document width is 1000px and the random number turned out to be 90% (=900px), since the box width is 200px, so you will have 100px out of the screen.
Now you have two solutions:
First: As #Sidd noted, you can check whether the box is in or out using getBoundingClientRect this will return a variable for you having two properties one is bottom which is the distance of the box from the upper edge of the document, the other property is height which is the distance of the box from the left border of the screen.
Now what you can do is compare those two values with the document width and height.
Now by adding those three lines you'll have your problem solved:
var rect = object.getBoundingClientRect(); // getting the properties
if(rect.right > window.innerWidth // comparing width
|| rect.bottom > window.innerHeight) // comparing height
{reappear(object);} // re-randomizing
https://jsfiddle.net/y3m4ygow/2/
this WILL work, but it might produce some flickering with some browsers, and i'm not very comfortable about calling a function inside itself.
Second Solution is: which is what I would prefer you to do, is by not using a percentage, but using a fixed height and width values.
you can get the current height and weight values from the window object and substract your box dimensions from it:
var cHeight = window.innerHeight - 200;
var cWidth = window.innerWidth - 200;
set those two as the maximum value for the top and the right.
function topRandomNum() {
var r = Math.random();
var y = (r * (cHeight - 0 + 1)) + 0;
var x = Math.floor(y);
return x;
}
function rightRandomNum() {
var r = Math.random();
var y = (r * (cWidth - 0 + 1)) + 0;
var x = Math.floor(y);
return x;
}
and here's the fiddle for the second solution: https://jsfiddle.net/uL24u0e4/
I am having problems measure the height of a font which I have included with CSS using this code:
measureFontHeight3: function(font)
{
var left = 0;
var top = 0;
var height = 50;
var width = 50;
// Draw the text in the specified area
var canvas = ig.$new('canvas');
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext('2d');
ctx.font = font;
ctx.textBaseline = 'top';
ctx.fillText('gM', 0,0);
// Get the pixel data from the canvas
var data = ctx.getImageData(left, top, width, height).data,
first = false,
last = false,
r = height,
c = 0;
// Find the last line with a non-white pixel
while(!last && r)
{
r--;
for(c = 0; c < width; c++)
{
if(data[r * width * 4 + c * 4 + 3])
{
last = r;
break;
}
}
}
// Find the first line with a non-white pixel
while(r)
{
r--;
for(c = 0; c < width; c++)
{
if(data[r * width * 4 + c * 4 + 3]) {
first = r;
break;
}
}
// If we've got it then return the height
if(first != r)
{
var result = last - first;
console.log("3: " +result);
return result;
}
}
// We screwed something up... What do you expect from free code?
return 0;
},
When I measure a font which the system already has installed, the function is quite accurate, but when I try to measure a font which I have included in a CSS file, the measurement does not work, i.e. it measure wrongly.
Is it because of the new canvas not being able to "see" the new font or is something else wrong ?
Could it be because you want to measure the font before it's been fully loaded ?
In my example it seems to be working fine : Font example
I'm trying to implement pinch-to-zoom gestures exactly as in Google Maps. I watched a talk by Stephen Woods - "Creating Responsive HTML5 Touch Interfaces” - about the issue and used the technique mentioned. The idea is to set the transform origin of the target element at (0, 0) and scale at the point of the transform. Then translate the image to keep it centered at the point of transform.
In my test code scaling works fine. The image zooms in and out fine between subsequent translations. The problem is I am not calculating the translation values properly. I am using jQuery and Hammer.js for touch events. How can I adjust my calculation in the transform callback so that the image stays centered at the point of transform?
The CoffeeScript (#test-resize is a div with a background image)
image = $('#test-resize')
hammer = image.hammer ->
prevent_default: true
scale_treshold: 0
width = image.width()
height = image.height()
toX = 0
toY = 0
translateX = 0
translateY = 0
prevScale = 1
scale = 1
hammer.bind 'transformstart', (event) ->
toX = (event.touches[0].x + event.touches[0].x) / 2
toY = (event.touches[1].y + event.touches[1].y) / 2
hammer.bind 'transform', (event) ->
scale = prevScale * event.scale
shiftX = toX * ((image.width() * scale) - width) / (image.width() * scale)
shiftY = toY * ((image.height() * scale) - height) / (image.height() * scale)
width = image.width() * scale
height = image.height() * scale
translateX -= shiftX
translateY -= shiftY
css = 'translateX(' + #translateX + 'px) translateY(' + #translateY + 'px) scale(' + scale + ')'
image.css '-webkit-transform', css
image.css '-webkit-transform-origin', '0 0'
hammer.bind 'transformend', () ->
prevScale = scale
I managed to get it working.
jsFiddle demo
In the jsFiddle demo, clicking on the image represents a pinch gesture centred at the click point. Subsequent clicks increase the scale factor by a constant amount. To make this useful, you would want to make the scale and translate computations much more often on a transform event (hammer.js provides one).
The key to getting it to work was to correctly compute the point of scale coordinates relative to the image. I used event.clientX/Y to get the screen coordinates. The following lines convert from screen to image coordinates:
x -= offset.left + newX
y -= offset.top + newY
Then we compute a new size for the image and find the distances to translate by. The translation equation is taken from Stephen Woods' talk.
newWidth = image.width() * scale
newHeight = image.height() * scale
newX += -x * (newWidth - image.width) / newWidth
newY += -y * (newHeight - image.height) / newHeight
Finally, we scale and translate
image.css '-webkit-transform', "scale3d(#{scale}, #{scale}, 1)"
wrap.css '-webkit-transform', "translate3d(#{newX}px, #{newY}px, 0)"
We do all our translations on a wrapper element to ensure that the translate-origin stays at the top left of our image.
I successfully used that snippet to resize images on phonegap, using hammer and jquery.
If it interests someone, i translated this to JS.
function attachPinch(wrapperID,imgID)
{
var image = $(imgID);
var wrap = $(wrapperID);
var width = image.width();
var height = image.height();
var newX = 0;
var newY = 0;
var offset = wrap.offset();
$(imgID).hammer().on("pinch", function(event) {
var photo = $(this);
newWidth = photo.width() * event.gesture.scale;
newHeight = photo.height() * event.gesture.scale;
// Convert from screen to image coordinates
var x;
var y;
x -= offset.left + newX;
y -= offset.top + newY;
newX += -x * (newWidth - width) / newWidth;
newY += -y * (newHeight - height) / newHeight;
photo.css('-webkit-transform', "scale3d("+event.gesture.scale+", "+event.gesture.scale+", 1)");
wrap.css('-webkit-transform', "translate3d("+newX+"px, "+newY+"px, 0)");
width = newWidth;
height = newHeight;
});
}
I looked all over the internet, and outernet whatever, until I came across the only working plugin/library - http://cubiq.org/iscroll-4
var myScroll;
myScroll = new iScroll('wrapper');
where wrapper is your id as in id="wrapper"
<div id="wrapper">
<img src="smth.jpg" />
</div>
Not a real answer, but a link to a plug=in that does it all for you. Great work!
https://github.com/timmywil/jquery.panzoom
(Thanks 'Timmywil', who-ever you are)
This is something I wrote a few years back in Java and recently converted to JavaScript
function View()
{
this.pos = {x:0,y:0};
this.Z = 0;
this.zoom = 1;
this.scale = 1.1;
this.Zoom = function(delta,x,y)
{
var X = x-this.pos.x;
var Y = y-this.pos.y;
var scale = this.scale;
if(delta>0) this.Z++;
else
{
this.Z--;
scale = 1/scale;
}
this.zoom = Math.pow(this.scale, this.Z);
this.pos.x+=X-scale*X;
this.pos.y+=Y-scale*Y;
}
}
The this.Zoom = function(delta,x,y) takes:
delta = zoom in or out
x = x position of the zoom origin
y = y position of the zoom origin
A small example:
<script>
var view = new View();
var DivStyle = {x:-123,y:-423,w:300,h:200};
function OnMouseWheel(event)
{
event.preventDefault();
view.Zoom(event.wheelDelta,event.clientX,event.clientY);
div.style.left = (DivStyle.x*view.zoom+view.pos.x)+"px";
div.style.top = (DivStyle.y*view.zoom+view.pos.y)+"px";
div.style.width = (DivStyle.w*view.zoom)+"px";
div.style.height = (DivStyle.h*view.zoom)+"px";
}
function OnMouseMove(event)
{
view.pos = {x:event.clientX,y:event.clientY};
div.style.left = (DivStyle.x*view.zoom+view.pos.x)+"px";
div.style.top = (DivStyle.y*view.zoom+view.pos.y)+"px";
div.style.width = (DivStyle.w*view.zoom)+"px";
div.style.height = (DivStyle.h*view.zoom)+"px";
}
</script>
<body onmousewheel="OnMouseWheel(event)" onmousemove="OnMouseMove(event)">
<div id="div" style="position:absolute;left:-123px;top:-423px;width:300px;height:200px;border:1px solid;"></div>
</body>
This was made with the intention of being used with a canvas and graphics, but it should work perfectly for normal HTML layout