I m using GD library to create images on the fly.
But when i rotate image using imagerotate() function
it works fine but it gives very much irritating rough edges of image
which is rotated.
as it is shown in this picture.
So how to make these sides/edges of rotated image smooth ?
One way to avoid from getting the Jaggies effect when rotating images is by using another way to sample the pixels than just taking the adjusted pixels, for example to use Nearest-neighbor interpolation to make the edges smoother. You can see matlab code example:
im1 = imread('lena.jpg');imshow(im1);
[m,n,p]=size(im1);
thet = rand(1);
mm = m*sqrt(2);
nn = n*sqrt(2);
for t=1:mm
for s=1:nn
i = uint16((t-mm/2)*cos(thet)+(s-nn/2)*sin(thet)+m/2);
j = uint16(-(t-mm/2)*sin(thet)+(s-nn/2)*cos(thet)+n/2);
if i>0 && j>0 && i<=m && j<=n
im2(t,s,:)=im1(i,j,:);
end
end
end
figure;
imshow(im2);
taken from (here). Basically it means that when sampling the pixels in the original picture, we sample near pixels and interpolate them to get the target pixel value. This way
you can achive waht you want withourt installing any additiional packages.
EDIT
I've found some old code I once wrote in Java, which contains implementations of a couple of sampling algorithems. Here is the code:
Nearest Neighbor sampler:
/**
* #pre (this!=null) && (this.pixels!=null)
* #post returns the sampled pixel of (x,y) by nearest neighbor sampling
*/
private Pixel sampleNearestNeighbor(double x, double y) {
int X = (int) Math.round(x);
int Y = (int) Math.round(y);
if (X >= 0 && Y >= 0 && X < this.pixels.length
&& Y < this.pixels[0].length)
// (X,Y) is within this.pixels' borders
return new Pixel(pixels[X][Y].getRGB());
else
return new Pixel(255, 255, 255);
// sample color will be default white
}
Bilinear sampler:
/**
* #pre (this!=null) && (this.pixels!=null)
* #post returns the sampled pixel of (x,y) by bilinear interpolation
*/
private Pixel sampleBilinear(double x, double y) {
int x1, y1, x2, y2;
x1 = (int) Math.floor(x);
y1 = (int) Math.floor(y);
double weightX = x - x1;
double weightY = y - y1;
if (x1 >= 0 && y1 >= 0 && x1 + 1 < this.pixels.length
&& y1 + 1 < this.pixels[0].length) {
x2 = x1 + 1;
y2 = y1 + 1;
double redAX = (weightX * this.pixels[x2][y1].getRed())
+ (1 - weightX) * this.pixels[x1][y1].getRed();
double greenAX = (weightX * this.pixels[x2][y1].getGreen())
+ (1 - weightX) * this.pixels[x1][y1].getGreen();
double blueAX = (weightX * this.pixels[x2][y1].getBlue())
+ (1 - weightX) * this.pixels[x1][y1].getBlue();
// bilinear interpolation of A point
double redBX = (weightX * this.pixels[x2][y2].getRed())
+ (1 - weightX) * this.pixels[x1][y2].getRed();
double greenBX = (weightX * this.pixels[x2][y2].getGreen())
+ (1 - weightX) * this.pixels[x1][y2].getGreen();
double blueBX = (weightX * this.pixels[x2][y2].getBlue())
+ (1 - weightX) * this.pixels[x1][y2].getBlue();
// bilinear interpolation of B point
int red = (int) (weightY * redBX + (1 - weightY) * redAX);
int green = (int) (weightY * greenBX + (1 - weightY) * greenAX);
int blue = (int) (weightY * blueBX + (1 - weightY) * blueAX);
// bilinear interpolation of A and B
return new Pixel(red, green, blue);
} else if (x1 >= 0
&& y1 >= 0 // last row or column
&& (x1 == this.pixels.length - 1 || y1 == this.pixels[0].length - 1)) {
return new Pixel(this.pixels[x1][y1].getRed(), this.pixels[x1][y1]
.getGreen(), this.pixels[x1][y1].getBlue());
} else
return new Pixel(255, 255, 255);
// sample color will be default white
}
Gaussian sampler:
/**
* #pre (this!=null) && (this.pixels!=null)
* #post returns the sampled pixel of (x,y) by gaussian function
*/
private Pixel sampleGaussian(double u, double v) {
double w = 3; // sampling distance
double sqrSigma = Math.pow(w / 3.0, 2); // sigma^2
double normal = 0;
double red = 0, green = 0, blue = 0;
double minIX = Math.round(u - w);
double maxIX = Math.round(u + w);
double minIY = Math.round(v - w);
double maxIY = Math.round(v + w);
for (int ix = (int) minIX; ix <= maxIX; ix++) {
for (int iy = (int) minIY; iy <= maxIY; iy++) {
double sqrD = Math.pow(ix - u, 2) + Math.pow(iy - v, 2);
// squared distance between (ix,iy) and (u,v)
if (sqrD < Math.pow(w, 2) && ix >= 0 && iy >= 0
&& ix < pixels.length && iy < pixels[0].length) {
// gaussian function
double gaussianWeight = Math.pow(2, -1 * (sqrD / sqrSigma));
normal += gaussianWeight;
red += gaussianWeight * pixels[ix][iy].getRed();
green += gaussianWeight * pixels[ix][iy].getGreen();
blue += gaussianWeight * pixels[ix][iy].getBlue();
}
}
}
red /= normal;
green /= normal;
blue /= normal;
return new Pixel(red, green, blue);
}
Actual rotate:
/**
* #pre (this!=null) && (this.pixels!=null) && (1 <= samplingMethod <= 3)
* #post creates a new rotated-by-degrees Image and returns it
*/
public myImage rotate(double degrees, int samplingMethod) {
myImage outputImg = null;
int t = 0;
for (; degrees < 0 || degrees >= 180; degrees += (degrees < 0) ? 180
: -180)
t++;
int w = this.pixels.length;
int h = this.pixels[0].length;
double cosinus = Math.cos(Math.toRadians(degrees));
double sinus = Math.sin(Math.toRadians(degrees));
int width = Math.round((float) (w * Math.abs(cosinus) + h * sinus));
int height = Math.round((float) (h * Math.abs(cosinus) + w * sinus));
w--;
h--; // move from (1,..,k) to (0,..,1-k)
Pixel[][] pixelsArray = new Pixel[width][height];
double x = 0; // x coordinate in the source image
double y = 0; // y coordinate in the source image
if (degrees >= 90) { // // 270 or 90 degrees turn
double temp = cosinus;
cosinus = sinus;
sinus = -temp;
}
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
double x0 = i;
double y0 = j;
if (degrees >= 90) {
if ((t % 2 == 1)) { // 270 degrees turn
x0 = j;
y0 = width - i - 1;
} else { // 90 degrees turn
x0 = height - j - 1;
y0 = i;
}
} else if (t % 2 == 1) { // 180 degrees turn
x0 = width - x0 - 1;
y0 = height - y0 - 1;
}
// calculate new x/y coordinates and
// adjust their locations to the middle of the picture
x = x0 * cosinus - (y0 - sinus * w) * sinus;
y = x0 * sinus + (y0 - sinus * w) * cosinus;
if (x < -0.5 || x > w + 0.5 || y < -0.5 || y > h + 0.5)
// the pixels that does not have a source will be painted in
// default white
pixelsArray[i][j] = new Pixel(255, 255, 255);
else {
if (samplingMethod == 1)
pixelsArray[i][j] = sampleNearestNeighbor(x, y);
else if (samplingMethod == 2)
pixelsArray[i][j] = sampleBilinear(x, y);
else if (samplingMethod == 3)
pixelsArray[i][j] = sampleGaussian(x, y);
}
}
outputImg = new myImage(pixelsArray);
}
return outputImg;
}
It may sound rather hack-ish, but it is the simplest way to do it, and even big enterprise solutions use this.
The trick is to first create the image 2X the size of what you need, then do all the drawing calls and then resize it to required original size.
Not only it is really easy to do, but also it is as fast as it gets and it produces very nice results. I use this trick for all cases when I need to apply blur to edges.
Another advantate to this is that it does not include blur on the rest of the image and it remains crisp clear - only borders of rotated image gets smoothed.
One thing you could try is to use imageantialias() to smooth the edges.
If that doesn't suit your needs, GD itself will probably not suffice.
GD uses very fast methods for all it's abilites without any actual smoothing or anything like that involved. If you want some proper image editing, you could either look into ImageMagick (which requires extra-software on the server) or write your own functions based on GD.
Keep in mind though, that php is really slow with huge amounts of data, so writing your own functions might be disappointing. (From my experience, PHP is roughly 40 times slower than compiled code.)
I recommend using ImageMagick for any image work where result quality matters.
Related
How do I calculate the percentage of increase or decrease of two numbers in PHP?
For example: (increase)100, (decrease)1 = -99%
(increase)1, (decrease)100 = +99%
Before anything else you need to have a solid understanding of the meaning of percentages and how they are computed.
The meaning of "x is 15% of y" is:
x = (15 * y) / 100
The arithmetic operations with percentages are similar. If a increases with 12% (of its current value) then:
a = a + (12 * a) / 10
Which is the same as:
a = 112 * a / 100
Subtracting 9% (of its current value) from b is:
b = b - (9 * b) / 100
or
b = b * 91 / 100
which actually is 91% of the value of b (100% - 9% of b).
Turn the above a, b, x, y into PHP variables (by placing $ in front of them), terminate the statements with semicolons (;) and you get valid PHP code that performs percentage operations.
PHP doesn't provide any particular function that helps working with percentages. As you can see above, there is no need for them.
My 2 cents ;)
Using PHP
function pctDiff($x1, $x2) {
$diff = ($x2 - $x1) / $x1;
return round($diff * 100, 2);
}
Usage:
$oldValue = 1000;
$newValue = 203.5;
$diff = pctDiff($oldValue, $newValue);
echo pctDiff($oldValue, $newValue) . '%'; // -79.65%
Using Swift 3
func pctDiff(x1: CGFloat, x2: CGFloat) -> Double {
let diff = (x2 - x1) / x1
return Double(round(100 * (diff * 100)) / 100)
}
let oldValue: CGFloat = 1000
let newValue: CGFloat = 203.5
print("\(pctDiff(x1: oldValue, x2: newValue))%") // -79.65%
I'm completely stuck with something that i believe is relatively simple to solve:
In PHP I am drawing filled rectangles on top of an image (using GD) - I want a small gap between each of them - which is specified in the code (gap is the same for each box) - I loop around until i've hit the max amount of rectangles on a row (e.g max columns) - and i've managed the horizontal gap offset but for the life of me can't work out the vertical gap maths.
Here is my code in it's nasty entirety:
http://pastebin.com/MHUqi0tG
But specifically PHP accepts two sets of coordinates to make a rectangle - two for the top left (x and y) and two for the bottom right (x and y).
Here is my code for that in particular:
$left_wall_of_box_x = ( $current_col * $box_size ) + $origin_x ;
if($last_block_x != $origin_x){
$left_wall_of_box_x = $last_block_x + $gap;
}
/*verticals coord 1*/
$left_wall_of_box_y = ( $current_row * $box_size ) + $origin_y;
$right_wall_x = $left_wall_of_box_x + $box_size ;
$right_wall_y = $left_wall_of_box_y + $box_size;
imagefilledrectangle($im, $left_wall_of_box_x, $left_wall_of_box_y, $right_wall_x, $right_wall_y, $red);
$gap = 2;
$origin_x & origin_y = the place i start drawing boxes from.
Output at the moment is nicely separated boxes on the horizontal axis but vertically they are hitting each other/merging together.
Any help obviously greatly appreciated - Tearing hair out on this.
Thanks
The mistake is here:
$left_wall_of_box_y = ( $current_row * $box_size ) + $origin_y;
Should be:
$left_wall_of_box_y = ( $current_row * ($box_size + $gap_vertical)) + $origin_y;
But your code is fragile and hard to read. It's fragile as you're making your calculations more be dependent on variables that represent subtly different things, and as there's more variables, it's harder to fit in your head - which is partly why you didn't spot the error.
So, rather than doing this:
while($x <= $total_boxes_to_draw)
{
if($current_col >= $cols)
{
//reset to start of row
}
}
Write your code like this.
$finished = false;
for ($y=0; ($y<$rows) && ($finished==false) ; $y++)
{
$yPosition = $origin_y + $y * ($box_size + $spacing_vertical);
for ($x=0 ; $x<$cols && ($finished==false) ; $x++)
{
$xPosition = $origin_x + $x * ($box_size + $spacing_horizontal);
//draw box at $xPosition, $yPosition
$boxesDrawn++;
if ($boxesDrawn >= $total_boxes_to_draw){
$finished = true;
}
}
}
You are adding a gap horizontally, but you also have to add it vertically.
A simple fix is subtracting the gap from the height of the rectangles you fill:
imagefilledrectangle($im, $left_wall_of_box_x, $left_wall_of_box_y,
$right_wall_x, $right_wall_y - gap, $red);
I'm looking for a way to combine a RGB color with a grayscale color.
I am looking into this for a gradient generator that uses stored colors and a template (pre-made style properties for most venders) to create a CSS3 gradient.
I am sure their is a simple solution, but I can not seem to find it. I am not looking for anyone to make me a custom function, I just need to know how to make the function.
Your general approach will probably consist of three parts:
Convert the RGB color to HSL.
Keep the hue and saturation, but apply the luminosity of the desired greyscale color.
Convert this HSL triple back to RGB.
Wikipedia has a great page on the HSL colorspace, including conversion formulas. This information was used to create some JavaScript conversion functions by Michael Jackson (yes, I'm serious):
rgbToHsl(r,g,b)
/**
* Converts an RGB color value to HSL. Conversion formula
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
* Assumes r, g, and b are contained in the set [0, 255] and
* returns h, s, and l in the set [0, 1].
*
* #param Number r The red color value
* #param Number g The green color value
* #param Number b The blue color value
* #return Array The HSL representation
*/
function rgbToHsl(r, g, b){
r /= 255, g /= 255, b /= 255;
var max = Math.max(r, g, b), min = Math.min(r, g, b);
var h, s, l = (max + min) / 2;
if(max == min){
h = s = 0; // achromatic
}else{
var d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch(max){
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return [h, s, l];
}
hslToRgb(h,s,l)
/**
* Converts an HSL color value to RGB. Conversion formula
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
* Assumes h, s, and l are contained in the set [0, 1] and
* returns r, g, and b in the set [0, 255].
*
* #param Number h The hue
* #param Number s The saturation
* #param Number l The lightness
* #return Array The RGB representation
*/
function hslToRgb(h, s, l){
var r, g, b;
if(s == 0){
r = g = b = l; // achromatic
}else{
function hue2rgb(p, q, t){
if(t < 0) t += 1;
if(t > 1) t -= 1;
if(t < 1/6) return p + (q - p) * 6 * t;
if(t < 1/2) return q;
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
return [r * 255, g * 255, b * 255];
}
Since you said you're not looking for anyone to make you a custom function, I trust you'll find little trouble adapting these functions to PHP and leveraging them for your needs. ;)
You can subtract the RGB channels with the brightness of the greyscale colour.
In javascript:
var grey = {r:123,g:123,b:123};
var color = {r:216,g:205,b:120};
var clamp = function(a,b,x){return Math.min(Math.max(a,x),b);}
var mixed = {
r:clamp(0,255,color.r-(255-grey.r)),
g:clamp(0,255,color.g-(255-grey.g)),
b:clamp(0,255,color.b-(255-grey.b))
};// the final combined color.
Hope this can help you.
I need to draw something like a subway map (multiple routes along the same path) using PHP's image library. Here's an example:
*********
******** *
******* * *
* * *
* * *
* * *
* * *
* * *
* * *
* * *
* * *
* * *
* * *
* * ********************
* *********************
**********************
It's easy enough to draw one line along this path. I don't know how to draw multiple lines that follow the path but have an equal amount of space between them.
For a given point A, and more lines through it, for the first points you'll have to decide whether points go 'inside'(B) the track, or 'outside'(C):
********C
D******A *
Q*****B * *
* * *
* E *
Now, you can calculate the offset of your point B to point A as a path from with length=offset (5px for instance) along the angle the which is half the clockwise angle between AE & AD for the 'inside' B (or the clockwise angle from AD to AE for the 'outside' C, or just use a negative offset later on). You'll want point B on a distance of 5px from A along the line through A with an angle angle AE + ((angle AD - angle AE) / 2)
I'm by no means a Math wiz, and the only time I needed to calculate angles like those were in javascript, I'll give it as an example, rewrite to PHP as you please (anybody who does know math, feel free to laugh & correct when needed):
var dx = b.x - a.x;
var dy = b.y - a.y;
if(dx == 0 && dy == 0){
answer = 0;
} else if(dx > 0 && dy >= 0 ){
answer = Math.atan(dy/dx);
} else if(dx <= 0 && dy > 0){
answer = Math.atan(dx/dy) + (Math.PI * 0.5);
} else if(dx <= 0 && dy <= 0){
answer = Math.atan(dy/dx) + Math.PI;
} else if(dx >= 0 && dy <= 0){
answer = Math.atan(dy/dx) + (Math.PI * 1.5);
}
So, in a grid where D=(0,10),A=(10,10), E=(20,20):
The angle through AE = 45° (PI/4 rad),through AD = 180° (PI rad)
The angle through AB is then (45 + ((180-45)/2))=> 112.5° (5/8 PI rad)
5px offset from A=(10,10) through angle 112.5° gives you this location for B:
Bx = Ax + (cos(angle) * 5) = +/- 8.1
By = Ay + (sin(angle) * 5) = +/- 14.6
At the 'sibling' point Q next to starting point D you have no previous path to reference / calculate an angle from, so I'd take the perpendicular: angle DQ = angle DA + 90° (PI/2 rad) (in the example you could just do Dy+5, but maybe you don't always start parallel to one of the 2 axis)
Rinse and repeat for all other points, draw lines between the calculated coordinates.
To complement Wrikken's answer, here's an actual code sample using Objective-C and the cocos2d-iphone engine reconstructed from this thread and others. The atan is not needed, instead the cross product is used, see the C function at the end of the code sample and this link.
I also simply switched the sign of the offset vector from A to B in order to get the vector from A to C. This avoids calling cosf/sinf twice.
PS: This code runs in a for loop from i = 0 to i < numVertices.
CGPoint splinePoint = splinePoints[i];
CGPoint prevPoint = (i == 0) ? splinePoint : splinePoints[i - 1];
CGPoint railPoint = splinePoint;
CGPoint nextPoint = (i == (numVertices-1)) ? splinePoint : splinePoints[i + 1];
CGPoint toPrevPoint = ccpSub(railPoint, prevPoint);
CGPoint toNextPoint = ccpSub(railPoint, nextPoint);
float angleToPrevPoint = ccpAngleSigned(kAngleOriginVector, toPrevPoint);
float angleToNextPoint = ccpAngleSigned(kAngleOriginVector, toNextPoint);
float offsetAngle = 0.0f;
if (i > 0 && i < (numVertices - 1))
{
offsetAngle = angleToNextPoint + ((angleToPrevPoint-angleToNextPoint) / 2);
}
else if (i == 0)
{
offsetAngle = angleToNextPoint + M_PI_2;
}
else
{
offsetAngle = angleToPrevPoint + M_PI_2;
}
CGPoint offsetLeftRail, offsetRightRail, offsetRail;
offsetRail.x = cosf(offsetAngle) * railOffsetFromCenter;
offsetRail.y = sinf(offsetAngle) * railOffsetFromCenter;
offsetLeftRail = ccpAdd(railPoint, offsetRail);
offsetRightRail = ccpAdd(railPoint, ccpMult(offsetRail, -1.0f));
if (isPointToTheLeftOfLine(prevPoint, railPoint, offsetLeftRail))
{
leftRailSplinePoints[i] = offsetLeftRail;
rightRailSplinePoints[i] = offsetRightRail;
}
else
{
leftRailSplinePoints[i] = offsetRightRail;
rightRailSplinePoints[i] = offsetLeftRail;
}
BOOL isPointToTheLeftOfLine(CGPoint start, CGPoint end, CGPoint test)
{
return ((end.x - start.x) * (test.y - start.y) -
(end.y - start.y) * (test.x - start.x)) > 0;
}
This helped me to draw the rails on the railtrack:
I have the Lat/Long value of New York City, NY; 40.7560540,-73.9869510 and a flat image of the earth, 1000px × 446px.
I would like to be able to convert, using Javascript, the Lat/Long to an X,Y coordinate where the point would reflect the location.
So the X,Y coordinate form the Top-Left corner of the image would be; 289, 111
Things to note:
don't worry about issues of what projection to use, make your own
assumption or go with what you know
might work
X,Y can be form any corner of the image
Bonus points for
the same solution in PHP (but I
really need the JS)
The projection you use is going to change everything, but this will work assuming a Mercator projection:
<html>
<head>
<script language="Javascript">
var dot_size = 3;
var longitude_shift = 55; // number of pixels your map's prime meridian is off-center.
var x_pos = 54;
var y_pos = 19;
var map_width = 430;
var map_height = 332;
var half_dot = Math.floor(dot_size / 2);
function draw_point(x, y) {
dot = '<div style="position:absolute;width:' + dot_size + 'px;height:' + dot_size + 'px;top:' + y + 'px;left:' + x + 'px;background:#00ff00"></div>';
document.body.innerHTML += dot;
}
function plot_point(lat, lng) {
// Mercator projection
// longitude: just scale and shift
x = (map_width * (180 + lng) / 360) % map_width + longitude_shift;
// latitude: using the Mercator projection
lat = lat * Math.PI / 180; // convert from degrees to radians
y = Math.log(Math.tan((lat/2) + (Math.PI/4))); // do the Mercator projection (w/ equator of 2pi units)
y = (map_height / 2) - (map_width * y / (2 * Math.PI)) + y_pos; // fit it to our map
x -= x_pos;
y -= y_pos;
draw_point(x - half_dot, y - half_dot);
}
</script>
</head>
<body onload="plot_point(40.756, -73.986)">
<!-- image found at http://www.math.ubc.ca/~israel/m103/mercator.png -->
<img src="mercator.png" style="position:absolute;top:0px;left:0px">
</body>
</html>
A basic conversion function in js would be:
MAP_WIDTH = 1000;
MAP_HEIGHT = 446;
function convert(lat, lon){
var y = ((-1 * lat) + 90) * (MAP_HEIGHT / 180);
var x = (lon + 180) * (MAP_WIDTH / 360);
return {x:x,y:y};
}
This will return the number of pixels from upper left.
This function assumes the following:
That your image is properly aligned
with the upper left corner (0,0)
aligning with 90* North by 180*
West.
That your coords are signed with N being -, S being +, W being - and E being +
If you have a picture of the whole earth, the projection does always matter. But maybe I just don't understand your question.