How do I specify font height at different orientations? - printing

The common way to create a font with GDI is to use the desired point size and the target device's vertical resolution (DPI) like this:
LOGFONT lf = {0};
lf.lfHeight = -MulDiv(point_size, GetDeviceCaps(hdc, LOGPIXELSY), 72);
...
HFONT hfont = CreateFontIndirect(&lf);
Assuming the default MM_TEXT mapping mode, this converts point_size into the pixel height for the desired device. (This is a common approximation. There are actually 72.27 points in an inch, not 72.) (The minus sign means I want to specify the actual character height, not the cell height.)
If I want to create a sideways font--that is, one with an orientation and escapement of 90 degrees--do I use LOGPIXELSX rather than LOGPIXELSY? For some of the printers I'm targeting, the horizontal and vertical resolutions are different.
Generally, if I want an angle of theta, do I combine LOGPIXELSX and LOGPIXELSY? I'm thinking of something like this:
// Given theta in degrees (e.g., theta = 45.0) ...
double theta_radians = theta * 2.0 * pi / 360.0;
int dpi = static_cast<int>(GetDeviceCaps(hdc, LOGPIXELSX) * sin(theta_radians) +
GetDeviceCaps(hdc, LOGPIXELSY) * cos(theta_radians) +
0.5);
LOGFONT lf = {0};
lf.lfHeight = -MulDiv(point_size, dpi, 72);
// Set escapement and orientation to theta in tenths of a degree.
lf.lfEscapement = lf.lfOrientation = static_cast<LONG>(theta * 10.0 + 0.5);
...
This makes intuitive sense to me, but I'm wondering if this is really how the GDI font mapper and printer drivers work.

1) There are 72 points/inch. (it used to be 72.27 but was changed.)
2) Combining LOGPIXELSX and LOGPIXELSY in the way that you do is fine, but
3) The font mapper doesn't look at escapement and orientation when mapping fonts. The LOGPIXELS values will only be used as part of the coordinate transformation.
http://msdn.microsoft.com/en-us/library/ms969909(loband).aspx
Not sure about how the "printer drivers work" because the statement could include many possible drivers and printers.
They could rasterize with square pixels, then stretch to non-square. They could transform glyph curves. They could do something else.

Related

How to get the rendered/displayed size of a scaled image/frame in Roblox Studio

I need to get the width and height of an image within a frame. Both the frame and image use the Scale property instead of the Offset property to set the size. I have an UIAspectRatioConstraint on the frame that the image is in. Everything scales with the screen size just fine.
However, I need to be able to get the current width/height of the image (or the frame) so that I can perform some math functions in order to move a marker over the image to a specific position (X, Y). I cannot get the size of the image/frame, and therefore cannot update the position.
Is there a way to get the currently rendered width of an image or frame that is using the Scale size options with the UIAspectRatioConstraint?
I'm sleepy. I hope this makes sense...
My current math for getting a position on another image that uses Offset instead of Size is:
local _x = (_miniMapImageSize.X.Offset / _worldCenterSize.X) * (_playerPos.X - _worldCenterPos.X) + (_miniMapFrameSize.X.Offset / 2)
local _y = (_miniMapImageSize.Y.Offset / _worldCenterSize.Z) * (_playerPos.Z - _worldCenterPos.Z) + (_miniMapFrameSize.Y.Offset / 2)
Which gives me the player position within my mini-map. But that doesn't scale. The actual map does, and I need to position the player's marker on that map as well.
Work-Around
For now (for anyone else looking for a solution), I have created a work-around. I now specify my actual image size:
local _mapSize = Vector2.new(814, 659)
Then I use the screen width and height to decide if I need to scale based off the x-axis or the y-axis. (Scale my math formula, not the image.)
if (_mouse.ViewSizeX / _mouse.ViewSizeY) - (_mapSize.X / _mapSize.Y) <= 0 then
-- If the width of the screen is at the same or smaller ratio with the height of the screen
-- then calculate the new size based off the width
local _smallerByPercent = (_mouse.ViewSizeX * 0.9) / _mapSize.X
_mapWidth = _mapSize.X * _smallerByPercent
mapHeight = _mapSize.Y * _smallerByPercent
else
local _smallerByPercent = (_mouse.ViewSizeY * 0.9) / _mapSize.Y
_mapWidth = _mapSize.X * _smallerByPercent
_mapHeight = _mapSize.Y * _smallerByPercent
end
After that, I can create the position for my marker on my map.
_x = ((_mapWidth / _worldCenterSize.X) * (_playerPos.X - _worldCenterPos.X)) * -1
_y = ((_mapHeight / _worldCenterSize.Z) * (_playerPos.Z - _worldCenterPos.Z)) * -1
_mapCharacterArrow.Position = UDim2.new(0.5, _x, 0.5, _y)
Now my marker is able to be placed where my character is within the larger map opened when I press "M".
HOWEVER
I would still love to know of a way to get the rendered/displayed image size... I was trying to make it to where I did not have to enter the image size into the script manually. I want it to be dynamic.
So it seems there is a property of most GUI elements called AbsoluteSize. This is the actual display size of the element, no matter what it is scaled to. (It is not that it stays the same when scaled, but it changes as it is scaled to give you the new size.)
With this, I was able to re-write my code to:
local _x = (_mapImageSize.X / _worldCenterSize.X) * (_playerPos.X - _worldCenterPos.X) * -1
local _y = (_mapImageSize.Y / _worldCenterSize.Z) * (_playerPos.Z - _worldCenterPos.Z) * -1
_mapCharacterArrow.Position = UDim2.new(0.5, _x, 0.5, _y)
Where _mapImageSize = [my map image].AbsoluteSize.
Much better than before.

Using CATransform3DRotate with perspective: how to correct the 2D size increase?

I'm trying to create a paper folding effect in Swift using CALayers and CATransform3DRotate. There are some libraries out there, but those are pretty outdated and don't fit my needs (they don't have symmetric folds, for example).
My content view controller will squeeze to the right half side of the screen, revealing the menu at the left side.
Everything went well, until I applied perspective: then the dimensions I calculate are not correct anymore.
To explain the problem, I created a demo to show you what I'm doing.
This the content view controller with three squares. I will use three folds, so each square will be on a separate fold.
The even folds will get anchor point (0, 0.5) and the odd folds will get anchor point (1, 0.5), plus they'll receive a shadow.
When fully folded, the content view will be half of the screen's width.
On an iPhone 7, each fold/plane will be 125 points unfolded and 62.5 points fully folded when looked at.
To calculate the rotation needed to achieve this 62.5 points width, we can use a trigonometric function. To illustrate, look at this top-down view:
We know the original plane size (125) and the 2D width (62.5), so we can calculate the angle α using arccos:
let angle = acos(width / originalWidth)
The result is 1.04719755 rad or 60 degrees.
When using this formula with CATransform3DRotate, I get the correct result:
Now for the problem: when I add perspective, my calculation isn't correct anymore. The planes are bigger. Probably because of the now different projection.
You can see the planes are now overlapping and being clipped.
I reconstructed the desired result on the right by playing with the angle, but the correction needed is not consistent, unfortunately.
Here's the code I use. It works perfectly without perspective.
// Loop layers
for i in 0..<self.layers.count {
// Get layer
let layer = self.layers[i]
// Get dimensions
let width = self.frame.size.width / CGFloat(self.numberOfFolds)
let originalWidth = self.sourceView.frame.size.width / CGFloat(self.numberOfFolds)
// Calculate angle
let angle = acos(width / originalWidth)
// Set transform
layer.transform = CATransform3DIdentity
layer.transform.m34 = 1.0 / -500
layer.transform = CATransform3DRotate(layer.transform, angle * (i % 2 == 0 ? -1 : 1), 0, 1, 0)
// Update position
if i % 2 == 0 {
layer.position = CGPoint(x: (width * CGFloat(i)), y: layer.position.y)
} else {
layer.position = CGPoint(x: (width * CGFloat(i + 1)), y: layer.position.y)
}
}
So my question is: how do I achieve the desired result? Do I need to correct the angle, or should I calculate the projected/2D width differently?
Thanks in advance! :)

Understanding CGAfflineTransform in the context of Rotation

I am working on a few experiments to learn gestures and animations in iOS. Creating a Tinder-like interface is one of them. I am following this guide: http://guti.in/articles/creating-tinder-like-animations/
I understand the changing of the position of the image, but don't understand the rotation. I think I've pinpointed my problem to not understanding CGAfflineTransform. Particularly, the following code:
CGFloat rotationStrength = MIN(xDistance / 320, 1);
CGFloat rotationAngle = (CGFloat) (2 * M_PI * rotationStrength / 16);
CGFloat scaleStrength = 1 - fabsf(rotationStrength) / 4;
CGFloat scale = MAX(scaleStrength, 0.93);
CGAffineTransform transform = CGAffineTransformMakeRotation(rotationAngle);
CGAffineTransform scaleTransform = CGAffineTransformScale(transform, scale, scale);
self.draggableView.transform = scaleTransform;
Where are these values and calculations, such as: 320, 1-fabs(strength) / 4 , .93, etc, coming from? How do they contribute to the eventual rotation?
On another note, Tinder seems to use a combination of swiping and panning. Do they add a swipe gesture to the image, or do they just take into account the velocity of the pan?
That code has a lot of magic constants, most of which are likely chosen because they resulted in something that "looked good". This can make it hard to follow. It's not so much about the actual transforms, but about the values used to create them.
Let's break it down, line by line, and see if that makes it clearer.
CGFloat rotationStrength = MIN(xDistance / 320, 1);
The value 320 is likely assumed to be the width of the device (it was the portrait width of all iPhones until the 6 and 6+ came out).
This means that xDistance / 320 is a factor of how far along the the x axis (based on the name xDistance) that the user has dragged. This will be 0.0 when the user hasn't dragged any distance and 1.0 when the user has dragged 320 points.
MIN(xDistance / 320, 1) Takes the smallest value of the dragged distance factor and 1). This means that if the user drags further than 320 points (so that the distance factor would be larger than 1, the rotation strength would never be larger than 1.0. It doesn't protect agains negative values (if the user dragged to the left, xDistance would be a negative value, which would always be smaller than 1. However, I'm not sure if the guide accounted for that (since 320 is the full width, not the half width.
So, the first line is a factor between 0 and 1 (assuming no negative values) of how much rotation should be applied.
CGFloat rotationAngle = (CGFloat) (2 * M_PI * rotationStrength / 16);
The next line calculates the actual angle of rotation. The angle is specified in radians. Since 2π is a full circle (360°), the rotation angle is ranging from 0 and 1/16 of a full circle (22.5°). Th value 1/16 is likely chosen because it "looked good".
The two lines together means that as the user drags further, the view rotates more.
CGFloat scaleStrength = 1 - fabsf(rotationStrength) / 4;
From the variable name, it would look like it would calculate how much the view should scale. But it's actually calculating what scale factor the view should have. A scale of 1 means the "normal" or unscaled size. When the rotation strength is 0 (when the xDistance is 0), the scale strength will be 1 (unscaled). As rotation strength increase, approaching 1, this scale factor approaches 0.75 (since that's 1 - 1/4).
fabsf is simply the floating point absolute value (fabsf(-0.3) is equal to 0.3)
CGFloat scale = MAX(scaleStrength, 0.93);
On the next line, the actual scale factor is calculated. It's simply the largest value of the scaleStrength and 0.93 (scaled down to 93%). The value 0.93 is completely arbitrary and is likely just what the author found appealing.
Since the scale strength ranges from 1 to 0.75 and the scale factor is never smaller than 0.93, the scale factor only changes for the first third of the xDistance. All scale strength values in the next two thirds will be smaller than 0.93 and thus won't change the scale factor.
With the scaleFactor and rotationAngle calculated as above, the view is first rotated (by that angle) and then scaled down (by that scale factor).
Summary
So, in short. As the view is dragged to the right (as xDistance approaches 320 points), The view linearly rotates from 0° to 22.5° over the full drag and scales from 100% to 93% over the first third of the drag (and then stays at 93% for the remainder of the drag gesture).

How to determine the width of the lines?

I need to detect the width of these lines:
These lines are parallel and have some noise on them.
Currently, what I do is:
1.Find the center using thinning (ZhangSuen)
ZhanSuenThinning(binImage, thin);
2.Compute the distance transform
cv::distanceTransform(binImage, distImg, CV_DIST_L2, CV_DIST_MASK_5);
3.Accumulate the half distance around the center
double halfWidth = 0.0;
int count = 0;
for(int a = 0; a < thinImg.cols; a++)
for(int b = 0; b < thinImg.rows; b++)
if(thinImg.ptr<uchar>(b, a)[0] > 0)
{
halfWidth += distImg.ptr<float>(b, a)[0];
count ++;
}
4.Finally, get the actual width
width = halfWidth / count * 2;
The result, isn't quite good, where it's wrong around 1-2 pixels. On bigger Image, the result is even worse, Any suggestion?
You can adapt barcode reader algorithms which is the faster way to do it.
Scan horizontal and vertical lines.
Lets X the length of the horizontal intersection with black line an Y the length of the vertical intersection (you can have it be calculating the median value of several X and Y if there are some noise).
X * Y / 2 = area
X²+Y² = hypotenuse²
hypotenuse * width / 2 = area
So : width = 2 * area / hypotenuse
EDIT : You can also easily find the angle by using PCA.
Al you need is find RotatedRect for each contour in your image, here is OpenCV tutorial how to do it. Then just take the values of 'size' from rotated rectangle where you will get height and width of contour, the height and width may interchange for different alignment of contour. Here in the above image the height become width and width become height.
Contour-->RotatedRect
|
'--> Size2f size
|
|-->width
'-->height
After find contour just do
RotatedRect minRect = minAreaRect( Mat(contours[i]) );
Size2f contourSize=minRect.size // width and height of the rectangle
Rotated rectangle for each contour
Here is C++ code
Mat src=imread("line.png",1);
Mat thr,gray;
blur(src,src,Size(3,3));
cvtColor(src,gray,CV_BGR2GRAY);
Canny(gray,thr,50, 190, 3, false );
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
findContours( thr.clone(),contours,hierarchy,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_SIMPLE,Point(0,0));
vector<RotatedRect> minRect( contours.size() );
for( int i = 0; i < contours.size(); i++ )
minRect[i] = minAreaRect( Mat(contours[i]) );
for( int i = 0; i< contours.size(); i++ )
{
cout<<" Size ="<<minRect[i].size<<endl; //The width may interchange according to contour alignment
Size2f s=minRect[i].size;
// rotated rectangle
Point2f rect_points[4]; minRect[i].points( rect_points );
for( int j = 0; j < 4; j++ )
line( src, rect_points[j], rect_points[(j+1)%4], Scalar(0,0,255), 1, 8 );
}
imshow("src",src);
imshow("Canny",thr);
One quick and simple suggestion:
Count the total number of black pixels.
Detect the length of each line. (perhaps with CVHoughLinesP, or simply the diagonal of the bounding box around each thinned line)
Divide the number of black pixels by the sum of all line lengths, that should give you the average line width.
I am not sure whether that is more accurate than your existing approach though. The irregular end parts of each line might throw it of.
One thing you could try that could increase the accuracy for that case:
Measure the average angle of the lines
Rotate the image so the lines are aligned horizontally
crop a rectangular subsection of your shape, so all lines have the same length
(you can get the contour of your shape by morphological closing, then find a rectangle that is entirely contained within the shape. Make sure that the horizontal edges of the rectangle are inbetween lines)
then count the number of black pixels again (count gray pixels caused by rotating the image as x% of a whole pixel)
Divide by (rectangle_width * number_of_lines_in_rectangle)
Hough line fits to find each line
From each pixel on each line fit, scan in the perpendicular direction to get the distance to the edge. Find the edge using a spline fit or similar sub-pixel method.
Depending on your needs/desires, take the median or average distance. To eliminate problems with outliers, throw out the distances below the 10th percentile and above the 90th percentile before calculating the mean or median. You might also report the size using statistics: line width W, standard deviation S.
Although a connected components algorithm can be used to find the lines, it won't find the "true" edges as nicely as a spline fit.
The image like you shown is noisy/blurry and thus the number of black pixels might not reflect line properties; for example, black pixels can be partially attributed to salt-and-pepper noise. You can get rid of it with morphological erosion but this will affect your lines as well.
A better way is to extract connected components, delete small ones that likely come from noise or small blobs, then calculate the number of pixels and divide it on the number of lines. This approach will also help you to analyse the shape of the objects in your image and get rid of any artefacts other than noise or lines.
A different real word situation is when you have some grey pixels close to a line border. You can either use a threshold to discard them or count them with some weight<1. This will compensate for blur in your image. By the way, rotation of the image may increase the blur since it is typically done with interpolation and smoothing.

Laplacian of gaussian filter use

This is a formula for LoG filtering:
(source: ed.ac.uk)
Also in applications with LoG filtering I see that function is called with only one parameter:
sigma(σ).
I want to try LoG filtering using that formula (previous attempt was by gaussian filter and then laplacian filter with some filter-window size )
But looking at that formula I can't understand how the size of filter is connected with this formula, does it mean that the filter size is fixed?
Can you explain how to use it?
As you've probably figured out by now from the other answers and links, LoG filter detects edges and lines in the image. What is still missing is an explanation of what σ is.
σ is the scale of the filter. Is a one-pixel-wide line a line or noise? Is a line 6 pixels wide a line or an object with two distinct parallel edges? Is a gradient that changes from black to white across 6 or 8 pixels an edge or just a gradient? It's something you have to decide, and the value of σ reflects your decision — the larger σ is the wider are the lines, the smoother the edges, and more noise is ignored.
Do not get confused between the scale of the filter (σ) and the size of the discrete approximation (usually called stencil). In Paul's link σ=1.4 and the stencil size is 9. While it is usually reasonable to use stencil size of 4σ to 6σ, these two quantities are quite independent. A larger stencil provides better approximation of the filter, but in most cases you don't need a very good approximation.
This was something that confused me too, and it wasn't until I had to do the same as you for a uni project that I understood what you were supposed to do with the formula!
You can use this formula to generate a discrete LoG filter. If you write a bit of code to implement that formula, you can then to generate a filter for use in image convolution. To generate, say a 5x5 template, simply call the code with x and y ranging from -2 to +2.
This will generate the values to use in a LoG template. If you graph the values this produces you should see the "mexican hat" shape typical of this filter, like so:
(source: ed.ac.uk)
You can fine tune the template by changing how wide it is (the size) and the sigma value (how broad the peak is). The wider and broader the template the less affected by noise the result will be because it will operate over a wider area.
Once you have the filter, you can apply it to the image by convolving the template with the image. If you've not done this before, check out these few tutorials.
java applet tutorials more mathsy.
Essentially, at each pixel location, you "place" your convolution template, centred at that pixel. You then multiply the surrounding pixel values by the corresponding "pixel" in the template and add up the result. This is then the new pixel value at that location (typically you also have to normalise (scale) the output to bring it back into the correct value range).
The code below gives a rough idea of how you might implement this. Please forgive any mistakes / typos etc. as it hasn't been tested.
I hope this helps.
private float LoG(float x, float y, float sigma)
{
// implement formula here
return (1 / (Math.PI * sigma*sigma*sigma*sigma)) * //etc etc - also, can't remember the code for "to the power of" off hand
}
private void GenerateTemplate(int templateSize, float sigma)
{
// Make sure it's an odd number for convenience
if(templateSize % 2 == 1)
{
// Create the data array
float[][] template = new float[templateSize][templatesize];
// Work out the "min and max" values. Log is centered around 0, 0
// so, for a size 5 template (say) we want to get the values from
// -2 to +2, ie: -2, -1, 0, +1, +2 and feed those into the formula.
int min = Math.Ceil(-templateSize / 2) - 1;
int max = Math.Floor(templateSize / 2) + 1;
// We also need a count to index into the data array...
int xCount = 0;
int yCount = 0;
for(int x = min; x <= max; ++x)
{
for(int y = min; y <= max; ++y)
{
// Get the LoG value for this (x,y) pair
template[xCount][yCount] = LoG(x, y, sigma);
++yCount;
}
++xCount;
}
}
}
Just for visualization purposes, here is a simple Matlab 3D colored plot of the Laplacian of Gaussian (Mexican Hat) wavelet. You can change the sigma(σ) parameter and see its effect on the shape of the graph:
sigmaSq = 0.5 % Square of σ parameter
[x y] = meshgrid(linspace(-3,3), linspace(-3,3));
z = (-1/(pi*(sigmaSq^2))) .* (1-((x.^2+y.^2)/(2*sigmaSq))) .*exp(-(x.^2+y.^2)/(2*sigmaSq));
surf(x,y,z)
You could also compare the effects of the sigma parameter on the Mexican Hat doing the following:
t = -5:0.01:5;
sigma = 0.5;
mexhat05 = exp(-t.*t/(2*sigma*sigma)) * 2 .*(t.*t/(sigma*sigma) - 1) / (pi^(1/4)*sqrt(3*sigma));
sigma = 1;
mexhat1 = exp(-t.*t/(2*sigma*sigma)) * 2 .*(t.*t/(sigma*sigma) - 1) / (pi^(1/4)*sqrt(3*sigma));
sigma = 2;
mexhat2 = exp(-t.*t/(2*sigma*sigma)) * 2 .*(t.*t/(sigma*sigma) - 1) / (pi^(1/4)*sqrt(3*sigma));
plot(t, mexhat05, 'r', ...
t, mexhat1, 'b', ...
t, mexhat2, 'g');
Or simply use the Wavelet toolbox provided by Matlab as follows:
lb = -5; ub = 5; n = 1000;
[psi,x] = mexihat(lb,ub,n);
plot(x,psi), title('Mexican hat wavelet')
I found this useful when implementing this for edge detection in computer vision. Although not the exact answer, hope this helps.
It appears to be a continuous circular filter whose radius is sqrt(2) * sigma. If you want to implement this for image processing you'll need to approximate it.
There's an example for sigma = 1.4 here: http://homepages.inf.ed.ac.uk/rbf/HIPR2/log.htm

Resources