Proportional image resize - language-agnostic

I'm having a little bit of a problem scaling my images to a properly predefined size. I was wondering - since it is purely mathematics, if there's some sort of common logical algorithm that works in every language (PHP, ActionScript, Javascript etc.) to scale images proportionally.
I'm using this at the moment:
var maxHeight = 300;
var maxWidth = 300;
var ratio:Number = height / width;
if (height > maxHeight) {
height = maxHeight;
width = Math.round(height / ratio);
}
else if(width > maxWidth) {
width = maxWidth;
height = Math.round(width * ratio);
}
But it doesn't work properly. The images scales proportionately, sure enough, but the size isn't set at 300 (either in width or in height). It kind of makes sense, but I was wondering if there's a fool-proof, easy way to scale images proportionally.

ratio = MIN( maxWidth / width, maxHeight/ height );
width = ratio * width;
height = ratio * height;
Make sure all divides are floating-point.

Dark Shikari has it. Your solution as stated in the question fails because you aren't first establishing which dimenson's size-to-maxsize ratio is greater and then reducing both dimensions by that greater ratio.
Your current solution's use of a serial, conditional analysis of one potential dimensional violation and then the other won't work.
Note also that if you want to upscale images, your current solution won't fly, and Dark Shikari's again will.

I'd recommend not writing this code yourself; there are myriads of pixel-level details that take a serious while to get right. Use ImageMagick, it's the best graphics library out there.

Here is how I do it:
+ (NSSize) scaleHeight:(NSSize)origSize
newHeight:(CGFloat)height {
NSSize newSize = NSZeroSize;
if ( origSize.height == 0 ) return newSize;
newSize.height = height;
CGFloat factor = ( height / origSize.height );
newSize.width = (origSize.width * factor );
return newSize;
}
+ (NSSize) scaleWidth:(NSSize)origSize
newWidth:(CGFloat)width {
NSSize newSize = NSZeroSize;
if ( origSize.width == 0 ) return newSize;
newSize.width = width;
CGFloat factor = ( width / origSize.width );
newSize.height = (origSize.height * factor );
return newSize;
}

Here's a function I've developed for my site, you might want to use. It's based on your answer above.
It does other things not only the image processing - please remove everything which is unnecessary.
<?php
$thumb_width = 500;
$thumb_height = 500;
if ($handle = opendir('to-do')) {
echo "Directory handle: $handle<br />";
echo "Files:<br /><br />";
/* This is the correct way to loop over the directory. */
while (false !== ($file = readdir($handle))) {
if ( ($file != ".") && ($file != "..") ){
echo "$file";
$original_path = "to-do/" . $file;
$source_image = ImageCreateFromJPEG( $original_path );
$thumb_width = $thumb_width;
$thumb_height = $thumb_height;
// Create the image, of the required size
$thumbnail = imagecreatetruecolor($thumb_width, $thumb_height);
if($thumbnail === false) {
//creation failed -- probably not enough memory
return null;
}
// Fill the image with a white color (this will be visible in the padding around the image,
// if the aspect ratios of the image and the thumbnail do not match)
// Replace this with any color you want, or comment it out for black.
// I used grey for testing =)
$fill = imagecolorallocate($thumbnail, 255, 255, 255);
imagefill($thumbnail, 0, 0, $fill);
// Compute resize ratio
$hratio = $thumb_height / imagesy($source_image);
$wratio = $thumb_width / imagesx($source_image);
$ratio = min($hratio, $wratio);
// If the source is smaller than the thumbnail size,
// Don't resize -- add a margin instead
// (that is, dont magnify images)
if ($ratio > 1.0)
$ratio = 1.0;
// Compute sizes
$sy = floor(imagesy($source_image) * $ratio);
$sx = floor(imagesx($source_image) * $ratio);
// Compute margins
// Using these margins centers the image in the thumbnail.
// If you always want the image to the top left, set both of these to 0
$m_y = floor(($thumb_height - $sy) / 2);
$m_x = floor(($thumb_width - $sx) / 2);
// Copy the image data, and resample
// If you want a fast and ugly thumbnail, replace imagecopyresampled with imagecopyresized
if (!imagecopyresampled($thumbnail, $source_image,
$m_x, $m_y, //dest x, y (margins)
0, 0, //src x, y (0,0 means top left)
$sx, $sy,//dest w, h (resample to this size (computed above)
imagesx($source_image), imagesy($source_image)) //src w, h (the full size of the original)
) {
//copy failed
imagedestroy($thumbnail);
return null;
}
/* Set the new file name */
$thumbnail_file_name = $file;
/* Apply changes on the original image and write the result on the disk */
ImageJPEG( $thumbnail, $complete_path . "done/" . $thumbnail_file_name );
unset($source_image);
unset($thumbnail);
unset($original_path);
unset($targeted_image_size);
echo " done<br />";
}
}
closedir($handle);
}
?>

well I made this function to scale proportional, it uses a given width, height, and optionally the max width/height u want (depends on the given width and height)
function scaleProportional($img_w,$img_h,$max=50)
{
$w = 0;
$h = 0;
$img_w > $img_h ? $w = $img_w / $img_h : $w = 1;
$img_h > $img_w ? $h = $img_h / $img_w : $h = 1;
$ws = $w > $h ? $ws = ($w / $w) * $max : $ws = (1 / $h) * $max;
$hs = $h > $w ? $hs = ($h / $h) * $max : $hs = (1 / $w) * $max;
return array(
'width'=>$ws,
'height'=>$hs
);
}
usage:
$getScale = scaleProportional(600,200,500);
$targ_w = $getScale['width']; //returns 500
$targ_h = $getScale['height']; //returns 16,6666667

Related

css font-size and line-height not matching the baseline

I'm trying to do something that should be very simple but I've spent my day between failures and forums..
I would like to adjust my font in order to match my baseline. On indesign it's one click but in css it looks like the most difficult thing on earth..
Lets take a simple example with rational values.
On this image I have a baseline every 20px.
So for my <body> I do:
<style>
body {font-size:16px; line-height:20px;}
</style>
Everything works perfectly. My paragraph matchs the baseline.
But when I'm scripting my <h> that doesn't match the baseline anymore.. what am I doing wrong? That should follow my baseline, shouldn't it?
<style type="text/css">
body{font-size: 16px; line-height: 20px;}
h1{font-size: 5em; line-height: 1.25em;}
h2{font-size: 4em; line-height: 1.25em;}
h3{font-size: 3em; line-height: 1.25em;}
h4{font-size: 2em; line-height: 1.25em;}
</style>
ps: 20/16=1.25em
In my inspector, computed returns the expected values
h1{font-size: 84px; line-height: 100px;}
h2{font-size: 68px; line-height: 80px;}
h3{font-size: 52px; line-height: 60px;}
h4{font-size: 36px; line-height: 40px;}
So that should display something like this no?
It is a bit complicated - you have to measure the fonts first (as InDesign does) and calculate "line-height", the thing you called "bottom_gap" and some other stuff
I'm pretty sure we can do something in JavaScript..
You are right – but for Typography JS is used to calculate the CSS (depending on the font metrics)
Did demo the first step (measuring a font) here
https://codepen.io/sebilasse/pen/gPBQqm
It is just showing graphically what is measured [for the technical background]
This measuring is needed because every font behaves totally different in a "line".
Here is a generator which could generate such a Typo CSS:
https://codepen.io/sebilasse/pen/BdaPzN
A function to measure could be based on <canvas> and look like this :
function getMetrics(fontName, fontSize) {
// NOTE: if there is no getComputedStyle, this library won't work.
if(!document.defaultView.getComputedStyle) {
throw("ERROR: 'document.defaultView.getComputedStyle' not found. This library only works in browsers that can report computed CSS values.");
}
if (!document.querySelector('canvas')) {
var _canvas = document.createElement('canvas');
_canvas.width = 220; _canvas.height = 220;
document.body.appendChild(_canvas);
}
// Store the old text metrics function on the Canvas2D prototype
CanvasRenderingContext2D.prototype.measureTextWidth = CanvasRenderingContext2D.prototype.measureText;
/**
* Shortcut function for getting computed CSS values
*/
var getCSSValue = function(element, property) {
return document.defaultView.getComputedStyle(element,null).getPropertyValue(property);
};
/**
* The new text metrics function
*/
CanvasRenderingContext2D.prototype.measureText = function(textstring) {
var metrics = this.measureTextWidth(textstring),
fontFamily = getCSSValue(this.canvas,"font-family"),
fontSize = getCSSValue(this.canvas,"font-size").replace("px",""),
isSpace = !(/\S/.test(textstring));
metrics.fontsize = fontSize;
// For text lead values, we meaure a multiline text container.
var leadDiv = document.createElement("div");
leadDiv.style.position = "absolute";
leadDiv.style.margin = 0;
leadDiv.style.padding = 0;
leadDiv.style.opacity = 0;
leadDiv.style.font = fontSize + "px " + fontFamily;
leadDiv.innerHTML = textstring + "<br/>" + textstring;
document.body.appendChild(leadDiv);
// Make some initial guess at the text leading (using the standard TeX ratio)
metrics.leading = 1.2 * fontSize;
// Try to get the real value from the browser
var leadDivHeight = getCSSValue(leadDiv,"height");
leadDivHeight = leadDivHeight.replace("px","");
if (leadDivHeight >= fontSize * 2) { metrics.leading = (leadDivHeight/2) | 0; }
document.body.removeChild(leadDiv);
// if we're not dealing with white space, we can compute metrics
if (!isSpace) {
// Have characters, so measure the text
var canvas = document.createElement("canvas");
var padding = 100;
canvas.width = metrics.width + padding;
canvas.height = 3*fontSize;
canvas.style.opacity = 1;
canvas.style.fontFamily = fontFamily;
canvas.style.fontSize = fontSize;
var ctx = canvas.getContext("2d");
ctx.font = fontSize + "px " + fontFamily;
var w = canvas.width,
h = canvas.height,
baseline = h/2;
// Set all canvas pixeldata values to 255, with all the content
// data being 0. This lets us scan for data[i] != 255.
ctx.fillStyle = "white";
ctx.fillRect(-1, -1, w+2, h+2);
ctx.fillStyle = "black";
ctx.fillText(textstring, padding/2, baseline);
var pixelData = ctx.getImageData(0, 0, w, h).data;
// canvas pixel data is w*4 by h*4, because R, G, B and A are separate,
// consecutive values in the array, rather than stored as 32 bit ints.
var i = 0,
w4 = w * 4,
len = pixelData.length;
// Finding the ascent uses a normal, forward scanline
while (++i < len && pixelData[i] === 255) {}
var ascent = (i/w4)|0;
// Finding the descent uses a reverse scanline
i = len - 1;
while (--i > 0 && pixelData[i] === 255) {}
var descent = (i/w4)|0;
// find the min-x coordinate
for(i = 0; i<len && pixelData[i] === 255; ) {
i += w4;
if(i>=len) { i = (i-len) + 4; }}
var minx = ((i%w4)/4) | 0;
// find the max-x coordinate
var step = 1;
for(i = len-3; i>=0 && pixelData[i] === 255; ) {
i -= w4;
if(i<0) { i = (len - 3) - (step++)*4; }}
var maxx = ((i%w4)/4) + 1 | 0;
// set font metrics
metrics.ascent = (baseline - ascent);
metrics.descent = (descent - baseline);
metrics.bounds = { minx: minx - (padding/2),
maxx: maxx - (padding/2),
miny: 0,
maxy: descent-ascent };
metrics.height = 1+(descent - ascent);
} else {
// Only whitespace, so we can't measure the text
metrics.ascent = 0;
metrics.descent = 0;
metrics.bounds = { minx: 0,
maxx: metrics.width, // Best guess
miny: 0,
maxy: 0 };
metrics.height = 0;
}
return metrics;
};
Note that you also need a good "reset.css" to reset the browser margins and paddings.
You click "show CSS" and you can also use the generated CSS to mix multiple fonts:
If they have different base sizes, normalize the second:
var factor = CSS1baseSize / CSS2baseSize;
and now recalculate each font in CSS2 with
var size = size * factor;
See a demo in https://codepen.io/sebilasse/pen/oENGev?editors=1100
What if it comes to images?
The following demo uses two fonts with the same metrics plus an extra JS part. It is needed to calculate media elements like images for the baseline grid :
https://codepen.io/sebilasse/pen/ddopBj

physics body moves in browser but not on mac or ios

i was trying to move the physics body by changing its coordinate in scheduler and i used this code. if i run this code in browser it will work but after js binding it doesn't work on mac or ios. Physics body doesn't move at all on these devices
init: function{
var mass = 1;
var width = 1, height = 1;
this.playerBody = new cp.Body(mass , cp.momentForBox(mass, width, height));
this.space.addBody(this.playerBody);
this.schedule(this.move);
},
move: function(dt){
this.space.step(dt);
this.playerBody.getPos().x += 2 * dt;
this.playerBody.getPos().y += 2 * dt;
}
Try removing that getPos() from those lines, leave them at: this.playerBody.p.x += 2 * dt;. I think that's most likely the cause of your problem.
Additionally, avoid manipulating the coordinates yourself and let the physics engine handle everything.
For example, you could assign the velocity by hand like this:
init: function{
var mass = 1;
var width = 1, height = 1;
var vx = 1, vy = 1;
this.playerBody = new cp.Body(mass , cp.momentForBox(mass, width, height));
this.space.addBody(this.playerBody);
this.playerBody.vx = vx;
this.playerBody.vy = vy;
this.schedule(this.move);
},
move: function(dt){
this.space.step(dt);
}
Or, if you want to give a "bump" to the object in a certain direction, you could use applyImpulse like this:
init: function{
var mass = 1;
var width = 1, height = 1;
var fx = 1, fy = 1;
this.playerBody = new cp.Body(mass , cp.momentForBox(mass, width, height));
this.space.addBody(this.playerBody);
this.playerBody.applyImpuse(cp.v(fx, fy), cp.v(0,0));
this.schedule(this.move);
},
move: function(dt){
this.space.step(dt);
}
Or, if you want to apply a constant force to the object, change applyImpulse to applyForce in that last example.
Note: the cp.v(0,0) parameter is telling the engine to apply the force to the center of the object, so it should not rotate.
PS: if (and only if) you happen to see some strange behaviour with the physics simulation, look at this answer.
after adding few lines my player started to move in mac
init: function{
var mass = 1;
var width = 1, height = 1;
this.playerBody = new cp.Body(mass , cp.momentForBox(mass, width, height));
this.space.addBody(this.playerBody);
this.schedule(this.move);
},
move: function(dt){
this.space.step(dt);
var a = this.playerBody.local2World(this.playerBody.p);
a.y += 2 * dt;
a.x += 2 * dt ;
a = this.playerBody.world2Local(a);
this.playerBody.p = a;
}
but i don't have explanation of this code

Canvas Zoom out Image Quality Loss

I written a code for zooming and zoom-out the canvas image but if try to zoom the image more than once the quality of image is changing.
This is my code using canvas2image plugin i am converting canvas to normal image.
Can any body please suggest me is there any thing wrong way i am doing.
function imgZoom(operation) {
var zoomImageObj = document.getElementById("originalImage");
var zoomedCanvas = $("#zoomCanvasPreview")[0];
var zoomedContext = zoomedCanvas.getContext("2d");
var selectedImgWidth = $('#originalImage').width();
var selectedImgHeight = $('#originalImage').height();
$("#zoomCanvasPreview").attr("height", selectedImgHeight);
$("#zoomCanvasPreview").attr("width", selectedImgWidth);
var previewHeight = $("#zoomCanvasPreview").height();
var previewWidth = $("#zoomCanvasPreview").width();
var zoomFactor = $("#zoomFactor option:selected").val();
// Making the zoomfactor string as float point then parsing for addition
// zoomFactor values if user selected 5%(0.05), 10%(0.1), 15(0.15) &etc
var zoomPercent = 1 + parseFloat(zoomFactor);
if(operation == 'zoomIn') {
newZoomWidth = Math.round(previewWidth * zoomPercent) ;
newZoomHeight = Math.round(previewHeight * zoomPercent) ;
} else if(operation == 'zoomOut'){
newZoomWidth = Math.round( previewWidth / zoomPercent );
newZoomHeight = Math.round( previewHeight / zoomPercent );
}
if( (newZoomWidth >= 0) && (newZoomHeight >= 0) )
{
$("#zoomCanvasPreview").attr("width", newZoomWidth);
$("#zoomCanvasPreview").attr("height", newZoomHeight);
// Drawing the image to canvas
zoomedContext.drawImage(zoomImageObj, 0, 0, imgWidth, imgHeight, 0, 0, newZoomWidth, newZoomHeight );
//return canvas.toDataURL("image/png");
var zoomedCanvas = $("#zoomCanvasPreview")[0];
oImgConvertExt = Canvas2Image.saveAsPNG(zoomedCanvas, true);
$("#originalImage").attr("src", oImgConvertExt.src);
$("#originalImage").attr("style", "display: none; visibility: hidden; width: "+newZoomWidth+"px; height: "+newZoomHeight+"px;");
} else {
alert("Reached maximum zoom out limit");
}
}
Image quality will not get changed If you use Images of format .SVG
these image will never get distorted even if you zoom IN or zoom OUT
There is no way around quality loss if you use canvas.drawImage().
Just zoom by changing the CSS width and height values. There won't be any quality loss then.
It is also a lot simpler to code...

How can I ammend this resize script to fill/crop to container?

I've got a simple script that resizes a loaded image to fit a specific width and height, however I want the option to be able to fill i.e. centre and crop to a specific width/height - any ideas on how I can modify this?
Current script:
function resizeImg(mc:MovieClip, maxW:Number, maxH:Number=0, constrainProportions:Boolean=true):void{
maxH = maxH == 0 ? maxW : maxH;
mc.width = maxW;
mc.height = maxH;
if (constrainProportions) {
mc.scaleX < mc.scaleY ? mc.scaleY = mc.scaleX : mc.scaleX = mc.scaleY;
}
}
I tried picking through the code of the DisplayUtils AS3 class from Soulwire (http://blog.soulwire.co.uk/flash/actionscript-3/fit-a-displayobject-into-a-rectangle) but its pretty well obfusticated and has no comments so im struggling :(
To do this you'll first need to store the original width and height so you can use it to scale the image. Besides that you'll need to mask the image and use your maxW and maxH values as it's dimensions. After that you'll be able to use a function like this:
function resizeImg(mc:Sprite, maxW:Number, maxH:Number = 0, constrainProportions:Boolean = true) : void
{
maxH = maxH == 0 ? maxW : maxH;
if(constrainProportions)
{
// First of we'll make the mc fit within the viewport
// calulate the difference between the max and stored dimensions
var dX:Number = maxW - originalWidth;
var dY:Number = maxH - originalHeight;
// evaluate values
if (dY > dX)
{
// mc is wider then maxW
// set width to max and scaleY to offset of width
mc.width = maxW;
mc.scaleY = mc.scaleX;
}
else
{
// mc is heigher then maxH
// set height to max and scaleX to offset of height
mc.height = maxH;
mc.scaleX = mc.scaleY;
}
// the mc now fits within the max viewport
// now we'll use the same trick to fill the resized mc in the max viewport
var dXcorrection:Number = maxW - mc.width;
var dYcorrection:Number = maxH - mc.height;
if (dYcorrection < dXcorrection)
{
mc.width = maxW;
mc.scaleY = mc.scaleX;
}
else
{
mc.height = maxH;
mc.scaleX = mc.scaleY;
}
// finally we'll center the resized mc within the max viewport
mc.x = (maxW - mc.width)/2;
mc.y = (maxH - mc.height)/2;
}
}

Insane Graphics.lineStyle behavior

I'd like some help with a little project of mine.
Background:
i have a little hierarchy of Sprite derived classes (5 levels starting from the one, that is the root application class in Flex Builder). Width and Height properties are overriden so that my class always remembers it's requested size (not just bounding size around content) and also those properties explicitly set scaleX and scaleY to 1, so that no scaling would ever be involved. After storing those values, draw() method is called to redraw content.
Drawing:
Drawing is very straight forward. Only the deepest object (at 1-indexed level 5) draws something into this.graphics object like this:
var gr:Graphics = this.graphics;
gr.clear();
gr.lineStyle(0, this.borderColor, 1, true, LineScaleMode.NONE);
gr.beginFill(0x0000CC);
gr.drawRoundRectComplex(0, 0, this.width, this.height, 10, 10, 0, 0);
gr.endFill();
Further on:
There is also MouseEvent.MOUSE_WHEEL event attached to the parent of the object that draws. What handler does is simply resizes that drawing object.
Problem:
Screenshot
When resizing sometimes that hairline border line with LineScaleMode.NONE set gains thickness (quite often even >10 px) + it quite often leaves a trail of itself (as seen in the picture above and below blue box (notice that box itself has one px black border)). When i set lineStile thickness to NaN or alpha to 0, that trail is no more happening.
I've been coming back to this problem and dropping it for some other stuff for over a week now.
Any ideas anyone?
P.S. Grey background is that of Flash Player itself, not my own choise.. :D
As requested, a bit more:
Application is supposed to be a calendar-timeline with a zooming "feature" (project for a course at university). Thus i have these functions that have something to do with resizing:
public function performZoom():void
{
// Calculate new width:
var newDayWidth:Number = view.width / 7 * this.calModel.zoom;
if (newDayWidth < 1)
{
newDayWidth = 1;
}
var newWidth:int = int(newDayWidth * timeline.totalDays);
// Calculate day element Height/Width ratio:
var headerHeight:Number = this.timeline.headerAllDay;
var proportion:Number = 0;
if (this.view.width != 0 && this.view.height != 0)
{
proportion = 1 / (this.view.width / 7) * (this.view.height - this.timeline.headerAllDay);
}
// Calculate new height:
var newHeight:int = int(newDayWidth * proportion + this.timeline.headerAllDay);
// Calculate mouse position scale on X axis:
var xScale:Number = 0;
if (this.timeline.width != 0)
{
xScale = newWidth / this.timeline.width;
}
// Calculate mouse position scale on Y axis:
var yScale:Number = 0;
if (this.timeline.height - this.timeline.headerAllDay != 0)
{
yScale = (newHeight - this.timeline.headerAllDay) / (this.timeline.height - this.timeline.headerAllDay);
}
this.timeline.beginUpdate();
// Resize the timeline
this.timeline.resizeElement(newWidth, newHeight);
this.timeline.endUpdate();
// Move timeline:
this.centerElement(xScale, yScale);
// Reset timeline local mouse position:
this.centerMouse();
}
public function resizeElement(widthValue:Number, heightValue:Number):void
{
var prevWidth:Number = this.myWidth;
var prevHeight:Number = this.myHeight;
if (widthValue != prevWidth || heightValue != prevHeight)
{
super.width = widthValue;
super.height = heightValue;
this.scaleX = 1.0;
this.scaleY = 1.0;
this.myHeight = heightValue;
this.myWidth = widthValue;
if (!this.docking)
{
this.origHeight = heightValue;
this.origWidth = widthValue;
}
this.updateAnchorMargins();
onResizeInternal(prevWidth, prevHeight, widthValue, heightValue);
}
}
Yes. I know.. a lot of core, and a lot of properties, but in fact most of the stuff has been disabled at the end and the situation is as described at the top.
this didn't work:
gr.lineStyle(); // Reset line style
Can we see your resizing code?
Also try clearing your line style as well as your fill:
gr.lineStyle(0, this.borderColor, 1, true, LineScaleMode.NONE);
gr.beginFill(0x0000CC);
gr.drawRoundRectComplex(0, 0, this.width, this.height, 10, 10, 0, 0);
gr.endFill();
gr.lineStyle();//<---- add this line
I don't know whether it's flash bug or what it is, but i finally found the solution.
The thing is that in my case when resizing in a nutshell you get like this:
this.width = this.stage.stageWidth / 7 * 365;
When i switch to maximized window this.width gains value 86k+. When i added this piece of code to draw horizontal line, it fixed itself:
var recWidth:Number = this.width;
var i:int;
var newEnd:Number = 0;
for (i = 1; newEnd < recWidth; i++)
{
newEnd = 100 * i;
if (newEnd > recWidth)
{
newEnd = recWidth;
}
gr.lineTo(newEnd, 0);
}
I don't know what is going on.. This is inconvenient...