How to work with readPixel and writePixel in JuicyPixels, Haskell? - image

In this article I've found some examples of using MutableImage with readPixel and writePixel functions, but I think it's too complicated, I mean, can I do that without ST Monad?
Let's say I have this
eDynamicImage <- readImage
eImage <- convertRGBA8 <$> eDynamicImage
so I can apply pixelMap based filters to that image like this negative <$> eImage, but how do I work with pixels and it's coordinates? Can someone explain me what is Mutable Image and how can I get this from DynamicImage or Image?
Is there any clean code that can rotate image using this function?
UPD 2.0: I've made totally working rotation function using built-in JuicyPixels generateImage function:
rotate :: Double -> Image PixelRGBA8 -> Image PixelRGBA8
rotate n img#Image {..} = generateImage rotater newW newH
where rotater x y = if srcX x y < imageWidth && srcX x y >= 0 && srcY x y < imageHeight && srcY x y >= 0
then pixelAt img (srcX x y) (srcY x y)
else PixelRGBA8 255 255 255 255
srcX x y = getX center + rounding (fromIntegral (x - getX newCenter) * cos' + fromIntegral (y - getY newCenter) * sin')
srcY x y = getY center + rounding (fromIntegral (y - getY newCenter) * cos' - fromIntegral (x - getX newCenter) * sin')
center = (imageWidth `div` 2, imageHeight `div` 2)
newCenter = (newW `div` 2, newH `div` 2)
newW = rounding $ abs (fromIntegral imageHeight * sin') + abs (fromIntegral imageWidth * cos')
newH = rounding $ abs (fromIntegral imageHeight * cos') + abs (fromIntegral imageWidth * sin')
sin' = sin $ toRad n
cos' = cos $ toRad n
where
rounding a = floor (a + 0.5)
getX = fst
getY = snd
toRad deg = deg * (pi/180)
That's it if someone need it. Thanks #Carsten for advice!

An MutableImage is one you can mutate (change in place) - Images are immutable by default. You'll need some kind of monad that allows that though (see the documentation - there are a few including ST and IO).
To get an MutableImage you can use thawImage - then you can work (get/set) pixels with readPixel and writePixel - after you can freezeImage again to get back an immutable Image
If you want to know how you can rotate images you can check the source code of rotateLeft :
rotateLeft90 :: Pixel a => Image a -> Image a
rotateLeft90 img#Image {..} =
generateImage gen imageHeight imageWidth
where
gen x y = pixelAt img (imageWidth - 1 - y) x
as you can see it does not use a mutable image but generates a new one from the olds pixels (using pixelAt instead of readPixel)
just in case - this is using the record-wildcards extension to extract all the fields from the Image a - parameter (that is what the Image {..} does - imageHeight,imageWidth is pulled from there)

Related

How to create a spherical angular gradient around y-axis using Lua in Filter Forge?

I am working on a Spherical Map Script that generates a linear gradient along the y-axis and an angular gradient around the y-axis in the green and red channels, respectively. I have been unable to find any documentation suited to this online, but have experimented using examples in Javascript and C#. The linear gradient has worked out fine, but the angular gradient (one describing a 360 degree arc around the y-axis) continues to elude me.
My working script is as follows.
-- 3d sphere
-- produces rgb gradudient mapped to a 3d sphere, but not correctly.
-- this is basically missing an angle gradient around the y-axis...
function prepare()
-- tilt & rotation precalc
toRad = 180/math.pi
-- toCir = 360/math.p -- may or may not work for circumference...
radius = get_slider_input(RADIUS)
angle = get_angle_input(ROTATION)/toRad
cosa = math.cos(angle)
sina = math.sin(angle)
tilt = get_angle_input(TILT)/toRad
cosa2 = math.cos(tilt)
sina2 = math.sin(tilt)
end;
function get_sample(x, y)
local r, g, b, a = get_sample_map(x, y, SOURCE)
-- color gradient example
-- local r = x
-- local g = y
-- local b = (x + y) / 2
-- local a = 1
-- spherical mapping formulae (polar to cartesian, apparently)
-- local x = x * math.pi -- * aspect
-- local y = y * math.pi
-- local nx = math.cos(x) * math.sin(y)
-- local ny = math.sin(x) * math.sin(y)
-- local nz = math.cos(y)
-- cartesian to polar (reference)
-- example 1
-- r = math.sqrt(((x * x) + (y * y) + (z * z)))
-- long = math.acos(x / math.sqrt((x * x) + (y * y))) * (y < 0 ? -1 : 1)
-- lat = math.acos(z / radius) * (z < 0 ? -1 : 1)
-- example 2
-- r = math.sqrt((x * x) + (y * y) + (z * z))
-- long = math.acos(x / math.sqrt((x * x) + (y * y))) * (y < 0 ? -1 : 1)
-- lat = math.acos(z / r)
-- equations cannot accept boolean comparison
-- boolean syntax may not be valid in lua
-- image generation
-- shift origin to center and set radius limits
local px = (x*2.0) - 1.0
local py = (y*2.0) - 1.0
px = px/radius
py = py/radius
local len = math.sqrt((px*px)+(py*py))
if len > 1.0 then return 0,0,0,0 end
local z = -math.sqrt(1.0 - ((px*px)+(py*py)))
-- cartesian to polar
-- r = math.sqrt((x * x) + (y * y) + (z * z))
-- lat = math.acos(z / r)
-- long = math.acos(x / math.sqrt((x * x) + (y * y))) * (y < 0) ? -1 : 1)
-- apply rotaqtion and tilt (order is important)
local tz = (cosa2 * z) - (sina2 * py)
local ty = (sina2 * z) + (cosa2 * py) -- gradient along y-axis is correct
z = tz
py = ty
local tx = (cosa * px) - (sina * z) -- gradient needs to go around y-axis
local tz = (sina * px) + (cosa * z)
px = tx
z = tz
-- r = math.sqrt((x * x) + (y * y) + (z * z))
-- lat = math.acos(z / r) -- invalid z for this; what is correct source?
-- long = math.acos(x / math.sqrt((x * x) + (y * y))) -- map -1 : 1 needed
-- long = (sina * px) + (cosa * z) -- ok; 2 full rotations
-- return r, g, b, a
-- return px,py,z,a
return px/2+.5,py/2+.5,z/2+.5,a
-- return px/2+.5,px/2+.5,px/2+.5,a
-- return long,long,long,a
-- return px/2+.5,py/2+.5,long,a
end;
I found a way to construct a spherical angular gradient from the hemispherical axis gradients I started with, by using three as the RBG channels in an RBG to HSL conversion. I've used this to map textures to spheres in Filter Forge in map scripts like this.
I thought I'd share the solution for anyone who's followed my question here.

algorithm of turning a distorted rectangle back to normal

I am thinking a way to transform a distorted rectangle back to normal but I have no idea how. I cannot find any information about it as well. Could anyone provide some information about how to do the trick only knowing the x, y position of the 4 junctions? Known the rectangle was 640 x 480 before distorted.
I think the problem can be solved by finding the transformation matrix in linear algebra. Since there is information about the original and the final matrix.
You have coordinates of 4 corners of distorted rectangle and want to transform it to some rectangle. In this case you need perspective transformation. Just define points for resulting rectangle as you want (for example, unit square in coordinate origin).
Now you have 8 pairs of corresponding parameters (x and y for every point), and need to calculate 8 parameters of matrix using 8 equations
//four pairs of such equations:
x' = (A * x + B * y + C) / (G * x + H * y + 1.0)
y' = (D * x + E * y + F) / (G * x + H * y + 1.0)
Theory of finding perspective transformation matrix is described in Paul Heckbert article.
C++ implementation could be found in antigrain library (file agg_trans_perspective.h)
Use OpenCV.
sample tip
Point lt = Point(200, 40) ;
Point rt = Point(500, 44) ;
Point rb = Point(740, 355) ;
Point lb = Point(30, 200) ;
vector<Point> rect ;
rect.push_back(lt) ;
rect.push_back(rt) ;
rect.push_back(rb) ;
rect.push_back(lb) ;
double w1 = sqrt(pow(rb.x-lb.x, 2)+pow(rb.y-lb.y, 2)) ;
double w2 = sqrt(pow(rt.x-lt.x, 2)+pow(rt.y-lt.y, 2)) ;
double h1 = sqrt(pow(rb.x-rt.x, 2)+pow(rb.y-rt.y, 2)) ;
double h2 = sqrt(pow(lb.x-lt.x, 2)+pow(lb.y-lt.y, 2)) ;
double maxW = (w1<w2)? w2 : w1 ;
double maxH = (h1<h2)? h2 : h1 ;
Point2f src[4], dst[4] ;
src[0]=Point2f(lt.x, lt.y) ;
src[1]=Point2f(rt.x, rt.y) ;
src[2]=Point2f(rb.x, rb.y) ;
src[4]=Point2f(lb.x, lb.y) ;
dst[0]=Point2f(0,0) ;
dst[1]=Point2f(maxW, 0) ;
dst[2]=Point2f(maxW, maxH) ;
dst[3]=Point2f(0, maxH) ;
tfMat = getPerspectiveTransform(src, dst) ;
wrapPerspective(srcImage, dstImage, trMat, Size(maxW, maxH)) ;

Getting dimension of specific segment window

I'm working on comparing the center of the blob with the 20% small box positioned at the center of the blob's bounding box.
I implemented this code first, to find the blob center points:
For y = 0 To bmp.ScaleHeight - 1
For x = 0 To bmp.ScaleWidth - 1
If bmp.Point(x, y) = vbWhite
Then
Xs = Xs + x
Ys = Ys + y
area = area + 1
endIF
Next x
Next y
YCenteroid = Ys / area
XCentroid = Xs / area
Then, the width and the height of the blob is calculated as below:
BlobHeight = MaxY - MinY
BlobWidth = MaxX - MinX
How to get that small box dimensions for comparing it with the center points?
Thanks
Coordinates of small box centered about (XCenteroid, YCenteroid) with width = 20% of blob width
RectLeft = XCentroid - 0.1 * BlobWidth
RectRight = XCentroid + 0.1 * BlobWidth
RectTop = YCentroid - 0.1 * BlobHeight
RectBottom = YCentroid + 0.1 * BlobHeight

Converting x/y coordinates to spherical

Given a flat (equarectangilar panoramic) image of for instance 6000px x 3000px (spreading 360 degrees wide and 180 degree high). How would I translate for instance x = -10, y=-10 to spherical coordinates (pan/tilt or vertical/horizontal offset) where the center of the image would mean a horizontal/vertical offset of 0?
Is it possible to calculate this, or do you need other variables like the radius, distance or z coordinate?
Edit:
What I have so far:
def self.translate_xy_to_spherical(x, y)
h = (x / (6000 / 360)) - 180
v = ((y / (3000 / 180)) - 90) / - 1
[h, v]
end
def self.translate_spherical_to_xy(h, v)
x = ((h + 180) * (6000 / 360))
y = ((v * -1) + 90) * (3000/ 180)
[x, y]
end
If I put in 0,0 in the first method, I get -180,90 which is correct. But if I set 3000,0 I'd expect 0,90 but I get 7,90. Same goes with the other formula (xy to spherical). When I input 0,0 I'd expect 3000,1500 but I get 2880x1440px. There is a small offset, probally because I calculate in a straight line.
Update: The Answer
I've updated the answer from below to take in account that degrees could be bigger than 360 degrees. I use the modulo to fix this:
IMAGE_WIDTH = 6000
IMAGE_HEIGHT = 3000
def self.translate_xy_to_spherical(x, y)
h = (x / (IMAGE_WIDTH / 360.0)) - 180
v = ((y / (IMAGE_HEIGHT / 180.0)) - 90) / -1
[h, v]
end
def self.translate_spherical_to_xy(h, v)
x = (((h % 360) + 180) * (IMAGE_WIDTH / 360.0))
y = (((v % 180) * -1) + 90) * (IMAGE_HEIGHT/ 180.0)
[x, y]
end
Your equations are mathematically correct, but when dividing integers in Ruby (and in most languages), you will lose the remainder. For example 6000/360 = 16.666... in real life, but in Ruby you'll get 16. All these rounding errors will lead to errors in your final result. A trick to avoid this avoid this is to make some of the numbers in your arithmetic are Floats instead of just Fixnums. Try:
def self.translate_xy_to_spherical(x, y)
h = (x / (6000 / 360.0)) - 180
v = ((y / (3000 / 180.0)) - 90) / - 1
[h, v]
end
def self.translate_spherical_to_xy(h, v)
x = ((h + 180) * (6000 / 360.0))
y = ((v * -1) + 90) * (3000/ 180.0)
[x, y]
end

Calculate largest inscribed rectangle in a rotated rectangle

I'm trying to find the best way to calculate the biggest (in area) rectangle which can be contained inside a rotated rectangle.
Some pictures should help (I hope) in visualizing what I mean:
The width and height of the input rectangle is given and so is the angle to rotate it. The output rectangle is not rotated or skewed.
I'm going down the longwinded route which I'm not even sure if it will handle the corner cases (no pun intended). I'm certain there is an elegant solution to this. Any tips?
EDIT: The output rectangle points don't necessarily have to touch the input rectangles edges. (Thanks to Mr E)
I just came here looking for the same answer. After shuddering at the thought of so much math involved, I thought I would resort to a semi-educated guess. Doodling a bit I came to the (intuitive and probably not entirely exact) conclusion that the largest rectangle is proportional to the outer resulting rectangle, and its two opposing corners lie at the intersection of the diagonals of the outer rectangle with the longest side of the rotated rectangle. For squares, any of the diagonals and sides would do... I guess I am happy enough with this and will now start brushing the cobwebs off my rusty trig skills (pathetic, I know).
Minor update... Managed to do some trig calculations. This is for the case when the Height of the image is larger than the Width.
Update. Got the whole thing working. Here is some js code. It is connected to a larger program, and most variables are outside the scope of the functions, and are modified directly from within the functions. I know this is not good, but I am using this in an isolated situation, where there will be no confusion with other scripts: redacted
I took the liberty of cleaning the code and extracting it to a function:
function getCropCoordinates(angleInRadians, imageDimensions) {
var ang = angleInRadians;
var img = imageDimensions;
var quadrant = Math.floor(ang / (Math.PI / 2)) & 3;
var sign_alpha = (quadrant & 1) === 0 ? ang : Math.PI - ang;
var alpha = (sign_alpha % Math.PI + Math.PI) % Math.PI;
var bb = {
w: img.w * Math.cos(alpha) + img.h * Math.sin(alpha),
h: img.w * Math.sin(alpha) + img.h * Math.cos(alpha)
};
var gamma = img.w < img.h ? Math.atan2(bb.w, bb.h) : Math.atan2(bb.h, bb.w);
var delta = Math.PI - alpha - gamma;
var length = img.w < img.h ? img.h : img.w;
var d = length * Math.cos(alpha);
var a = d * Math.sin(alpha) / Math.sin(delta);
var y = a * Math.cos(gamma);
var x = y * Math.tan(gamma);
return {
x: x,
y: y,
w: bb.w - 2 * x,
h: bb.h - 2 * y
};
}
I encountered some problems with the gamma-calculation, and modified it to take into account in which direction the original box is the longest.
-- Magnus Hoff
Trying not to break tradition putting the solution of the problem as a picture:)
Edit:
Third equations is wrong. The correct one is:
3.w * cos(α) * X + w * sin(α) * Y - w * w * sin(α) * cos(α) - w * h = 0
To solve the system of linear equations you can use Cramer rule, or Gauss method.
First, we take care of the trivial case where the angle is zero or a multiple of pi/2. Then the largest rectangle is the same as the original rectangle.
In general, the inner rectangle will have 3 points on the boundaries of the outer rectangle. If it does not, then it can be moved so that one vertex will be on the bottom, and one vertex will be on the left. You can then enlarge the inner rectangle until one of the two remaining vertices hits a boundary.
We call the sides of the outer rectangle R1 and R2. Without loss of generality, we can assume that R1 <= R2. If we call the sides of the inner rectangle H and W, then we have that
H cos a + W sin a <= R1
H sin a + W cos a <= R2
Since we have at least 3 points on the boundaries, at least one of these inequality must actually be an equality. Let's use the first one. It is easy to see that:
W = (R1 - H cos a) / sin a
and so the area is
A = H W = H (R1 - H cos a) / sin a
We can take the derivative wrt. H and require it to equal 0:
dA/dH = ((R1 - H cos a) - H cos a) / sin a
Solving for H and using the expression for W above, we find that:
H = R1 / (2 cos a)
W = R1 / (2 sin a)
Substituting this in the second inequality becomes, after some manipulation,
R1 (tan a + 1/tan a) / 2 <= R2
The factor on the left-hand side is always at least 1. If the inequality is satisfied, then we have the solution. If it isn't satisfied, then the solution is the one that satisfies both inequalities as equalities. In other words: it is the rectangle which touches all four sides of the outer rectangle. This is a linear system with 2 unknowns which is readily solved:
H = (R2 cos a - R1 sin a) / cos 2a
W = (R1 cos a - R2 sin a) / cos 2a
In terms of the original coordinates, we get:
x1 = x4 = W sin a cos a
y1 = y2 = R2 sin a - W sin^2 a
x2 = x3 = x1 + H
y3 = y4 = y2 + W
Edit: My Mathematica answer below is wrong - I was solving a slightly different problem than what I think you are really asking.
To solve the problem you are really asking, I would use the following algorithm(s):
On the Maximum Empty Rectangle Problem
Using this algorithm, denote a finite amount of points that form the boundary of the rotated rectangle (perhaps a 100 or so, and make sure to include the corners) - these would be the set S decribed in the paper.
.
.
.
.
.
For posterity's sake I have left my original post below:
The inside rectangle with the largest area will always be the rectangle where the lower mid corner of the rectangle (the corner near the alpha on your diagram) is equal to half of the width of the outer rectangle.
I kind of cheated and used Mathematica to solve the algebra for me:
From this you can see that the maximum area of the inner rectangle is equal to 1/4 width^2 * cosecant of the angle times the secant of the angle.
Now I need to figure out what is the x value of the bottom corner for this optimal condition. Using the Solve function in mathematica on my area formula, I get the following:
Which shows that the x coordinate of the bottom corner equals half of the width.
Now just to make sure, I'll going to test our answer empirically. With the results below you can see that indeed the highest area of all of my tests (definately not exhaustive but you get the point) is when the bottom corner's x value = half of the outer rectangle's width.
#Andri is not working correctly for image where width > height as I tested.
So, I fixed and optimized his code by such way (with only two trigonometric functions):
calculateLargestRect = function(angle, origWidth, origHeight) {
var w0, h0;
if (origWidth <= origHeight) {
w0 = origWidth;
h0 = origHeight;
}
else {
w0 = origHeight;
h0 = origWidth;
}
// Angle normalization in range [-PI..PI)
var ang = angle - Math.floor((angle + Math.PI) / (2*Math.PI)) * 2*Math.PI;
ang = Math.abs(ang);
if (ang > Math.PI / 2)
ang = Math.PI - ang;
var sina = Math.sin(ang);
var cosa = Math.cos(ang);
var sinAcosA = sina * cosa;
var w1 = w0 * cosa + h0 * sina;
var h1 = w0 * sina + h0 * cosa;
var c = h0 * sinAcosA / (2 * h0 * sinAcosA + w0);
var x = w1 * c;
var y = h1 * c;
var w, h;
if (origWidth <= origHeight) {
w = w1 - 2 * x;
h = h1 - 2 * y;
}
else {
w = h1 - 2 * y;
h = w1 - 2 * x;
}
return {
w: w,
h: h
}
}
UPDATE
Also I decided to post the following function for proportional rectange calculating:
calculateLargestProportionalRect = function(angle, origWidth, origHeight) {
var w0, h0;
if (origWidth <= origHeight) {
w0 = origWidth;
h0 = origHeight;
}
else {
w0 = origHeight;
h0 = origWidth;
}
// Angle normalization in range [-PI..PI)
var ang = angle - Math.floor((angle + Math.PI) / (2*Math.PI)) * 2*Math.PI;
ang = Math.abs(ang);
if (ang > Math.PI / 2)
ang = Math.PI - ang;
var c = w0 / (h0 * Math.sin(ang) + w0 * Math.cos(ang));
var w, h;
if (origWidth <= origHeight) {
w = w0 * c;
h = h0 * c;
}
else {
w = h0 * c;
h = w0 * c;
}
return {
w: w,
h: h
}
}
Coproc solved this problem on another thread (https://stackoverflow.com/a/16778797) in a simple and efficient way. Also, he gave a very good explanation and python code there.
Below there is my Matlab implementation of his solution:
function [ CI, T ] = rotateAndCrop( I, ang )
%ROTATEANDCROP Rotate an image 'I' by 'ang' degrees, and crop its biggest
% inner rectangle.
[h,w,~] = size(I);
ang = deg2rad(ang);
% Affine rotation
R = [cos(ang) -sin(ang) 0; sin(ang) cos(ang) 0; 0 0 1];
T = affine2d(R);
B = imwarp(I,T);
% Largest rectangle
% solution from https://stackoverflow.com/a/16778797
wb = w >= h;
sl = w*wb + h*~wb;
ss = h*wb + w*~wb;
cosa = abs(cos(ang));
sina = abs(sin(ang));
if ss <= 2*sina*cosa*sl
x = .5*min([w h]);
wh = wb*[x/sina x/cosa] + ~wb*[x/cosa x/sina];
else
cos2a = (cosa^2) - (sina^2);
wh = [(w*cosa - h*sina)/cos2a (h*cosa - w*sina)/cos2a];
end
hw = flip(wh);
% Top-left corner
tl = round(max(size(B)/2 - hw/2,1));
% Bottom-right corner
br = tl + round(hw);
% Cropped image
CI = B(tl(1):br(1),tl(2):br(2),:);
sorry for not giving a derivation here, but I solved this problem in Mathematica a few days ago and came up with the following procedure, which non-Mathematica folks should be able to read. If in doubt, please consult http://reference.wolfram.com/mathematica/guide/Mathematica.html
The procedure below returns the width and height for a rectangle with maximum area that fits into another rectangle of width w and height h that has been rotated by alpha.
CropRotatedDimensionsForMaxArea[{w_, h_}, alpha_] :=
With[
{phi = Abs#Mod[alpha, Pi, -Pi/2]},
Which[
w == h, {w,h} Csc[phi + Pi/4]/Sqrt[2],
w > h,
If[ Cos[2 phi]^2 < 1 - (h/w)^2,
h/2 {Csc[phi], Sec[phi]},
Sec[2 phi] {w Cos[phi] - h Sin[phi], h Cos[phi] - w Sin[phi]}],
w < h,
If[ Cos[2 phi]^2 < 1 - (w/h)^2,
w/2 {Sec[phi], Csc[phi]},
Sec[2 phi] {w Cos[phi] - h Sin[phi], h Cos[phi] - w Sin[phi]}]
]
]
Here is the easiest way to do this... :)
Step 1
//Before Rotation
int originalWidth = 640;
int originalHeight = 480;
Step 2
//After Rotation
int newWidth = 701; //int newWidth = 654; //int newWidth = 513;
int newHeight = 564; //int newHeight = 757; //int newHeight = 664;
Step 3
//Difference in height and width
int widthDiff ;
int heightDiff;
int ASPECT_RATIO = originalWidth/originalHeight; //Double check the Aspect Ratio
if (newHeight > newWidth) {
int ratioDiff = newHeight - newWidth;
if (newWidth < Constant.camWidth) {
widthDiff = (int) Math.floor(newWidth / ASPECT_RATIO);
heightDiff = (int) Math.floor((originalHeight - (newHeight - originalHeight)) / ASPECT_RATIO);
}
else {
widthDiff = (int) Math.floor((originalWidth - (newWidth - originalWidth) - ratioDiff) / ASPECT_RATIO);
heightDiff = originalHeight - (newHeight - originalHeight);
}
} else {
widthDiff = originalWidth - (originalWidth);
heightDiff = originalHeight - (newHeight - originalHeight);
}
Step 4
//Calculation
int targetRectanleWidth = originalWidth - widthDiff;
int targetRectanleHeight = originalHeight - heightDiff;
Step 5
int centerPointX = newWidth/2;
int centerPointY = newHeight/2;
Step 6
int x1 = centerPointX - (targetRectanleWidth / 2);
int y1 = centerPointY - (targetRectanleHeight / 2);
int x2 = centerPointX + (targetRectanleWidth / 2);
int y2 = centerPointY + (targetRectanleHeight / 2);
Step 7
x1 = (x1 < 0 ? 0 : x1);
y1 = (y1 < 0 ? 0 : y1);
This is just an illustration of Jeffrey Sax's solution above, for my future reference.
With reference to the diagram above, the solution is:
(I used the identity tan(t) + cot(t) = 2/sin(2t))

Resources