UIImage transform/scaling issues - ios

Finally, I have a reason to ask something, instead of scouring endless hours of the joys of Stack Overflow.
Here's my situation: I have an UIImageView with one UIImage inside it. I'm transforming the entire UIImageView via CGAffineTranforms, to scale it height-wise and keep it at a specific angle.
I'm feeding it this transform data through two CGPoints, so it's essentially just calculating the angle and scale between these two points and transforming.
Now, the transforming is working like a charm, but I recently came across the UIImage method resizableImageWithCapInsets, which works just fine if you set the frame of the image manually (ie. scale using a frame), but it seems that using transforms overrides this, which I guess is sort of to be expected since it's Core Graphics doing it's thing.
My question is, how would I go about either a) adding cap insets after transforming the image or b) doing the angle & scaling via a frame?
Note that the two points providing the data are touch points, so they can differ very much, which is why creating a scaled rectangle at a specific angle is tricky at best.
To keep you code hungry geniuses happy, here's a snippet of the current way I'm handling scaling (only doing cap insets when creating the UIImage):
float xDiff = (PointB.x - PointA.x) / 2;
float yDiff = (PointB.y - PointA.y) / 2;
float angle = [self getRotatingAngle:PointA secondPoint:PointB];
CGPoint pDiff = CGPointMake(PointA.x + xDiff, PointA.y + yDiff);
self.center = pDiff;
// Setup a new transform
// Set it up with a scale and an angle
double distance = sqrt(pow((PointB.x - PointA.x), 2.0) + pow((PointB.y - PointA.y), 2.0));
float scale = 1.0 * (distance / self.image.size.height);
CGAffineTransform transformer = self.transform;
transformer = CGAffineTransformConcat(CGAffineTransformMakeScale(1.0, scale), CGAffineTransformMakeRotation(angle));
// Apply the transformer
self.transform = transformer;

Adding a proper answer to this. The answer to the problem can be found here.

Related

UIViews with subviews: calculating position when scaling

I have a view that I draw using Core Graphics, which in this example is a segmented circle. The user can touch the circle to create a point along its circumference; this creates a subview on the UIView that contains the circle graphic.
Then I've implemented a pinch-zoom gesture which causes the circle to redraw to its new size. I've seen most implementations of pinch zoom use transform properties, but I've chosen to redraw because it's all vectors and gives a clean result.
My problem is repositioning the point views. I calculate the required position of those points based on the scale of the parent view: as it changes I update the x/y coords of the point views. However, it seems there are some precision issues: as the circle shape size increases, the points drift so they aren't right on the line anymore. Here's a couple examples:
This is where the circle is at 100% scale. Note the perfect positioning of that black point. But when you zoom in...
The point drifts off-line.
And here's some code. I derive the new size of the circle from the pinch gesture's scale (I modify if a bit to constrain and slow it down for UI purposes, so that's deltaScale) and then draw it like so:
let currentSize = self.shape!.bounds.size
let newSize = CGSize(width: self.originalSize.width * deltaScale, height: self.originalSize.height * deltaScale)
self.shape?.frame.size = newSize
self.shape?.center = self.originalCentre!
self.shape?.shapeSize = newSize
self.shape?.setNeedsDisplay()
As the pinch-zoom gesture completes, I calculate the factor:
let xScale = Double(newSize.width) / Double(currentSize.width)
let yScale = Double(newSize.height) / Double(currentSize.height)
self.points = self.points.map{(thisPoint) -> UIView in
thisPoint.center = CGPoint(x: Double(thisPoint.center.x) * xScale, y: Double(thisPoint.center.y) * yScale)
return thisPoint
}
(I was using CGFloats, but switched to Doubles in the hope that it would give me the precision I needed. Alas.)
You're accumulating roundoff errors. This is getting executed repeatedly:
thisPoint.center = CGPoint(x: Double(thisPoint.center.x) * xScale, y: Double(thisPoint.center.y) * yScale)
Repeating any calculation of the form 'x=f(x)' with anything less than unlimited precision will result in drift.
Trick is to not have 'thisPoint.center' on both sides of the equal sign. Best way to do that is to have thisPoint.center be a pure function of some other state. Commenter suggested storing desired angle, that would work well. Then you could do:
thisPoint.center = f(thisPoint.someRadians), where 'f' converts from polar to rectangular coordinates, factoring in the scale of the circle.

CorePlot allow user drag rotate PieChart

(As for I got the solution now, it is being shared at the bottom)
Fact is I have been struggling a while about this and I believe quite a lot of discussions I found are related to older versions of CorePlot or unanswered.
Firstly, I am using CorePlot 1.5.1.
I am able to plot a PieChart already and now I would like the user to be able to rotate it by dragging on the screen ( doesn't really matter touch directly the pieChart or the host View).
Using these delegates at the moment:
#interface MyChartViewController : UIViewController<CPTPieChartDataSource,CPTPlotSpaceDelegate,CPTPieChartDelegate>
Got a hostView,
#property (strong, nonatomic) IBOutlet CPTGraphHostingView *hostView;
Made a graph, set as, self.hostView.hostedGraph = graph
and made a PieChart, put into the graph, [graph addPlot:self.mainPieChart];
(I set the pieChart with a strong property to let me refer it anytime)
So, here is my first attempt, and fact is, it is responding, (though not in a desirable way)
CPTXYPlotSpace *plotSpace = (CPTXYPlotSpace *) self.hostView.hostedGraph.defaultPlotSpace;
[plotSpace setDelegate:self];
(only works by setting plotSpace delegate to self, not sure why, i guess it's about finding a way to receive user's interaction, anyway, then I overwrite these two functions)
Using this value:
static float deltaAngle;
-(BOOL)plotSpace:(CPTPlotSpace *)space shouldHandlePointingDeviceDownEvent:(UIEvent *)event atPoint:(CGPoint)point
{
float dx = point.x - self.mainPieChart.centerAnchor.x;
float dy = point.y - self.mainPieChart.centerAnchor.y;
deltaAngle = atan2(dy,dx);
return YES;
}
This, in order to save the first touching point
Then sense the dragging do use the difference to make the rotation
( at least I wanted so )
-(BOOL)plotSpace:(CPTPlotSpace *)space shouldHandlePointingDeviceDraggedEvent:(UIEvent *)event atPoint:(CGPoint)point
{
int x = self.mainPieChart.centerAnchor.x;
int y = self.mainPieChart.centerAnchor.y;
float dx = point.x - x;
float dy = point.y - y;
double a = atan2(dx,dy);
float angleDifference = deltaAngle - a;
self.mainPieChart.startAngle = -angleDifference;
return YES;
}
And here is an image about it, though i think I covered most of the details already.
http://postimg.org/image/bey0fosqj/
It is in landscape mode though.
Fact is I think this would be the most appropriate function to call, but somehow I cannot call it out (pretty sure I set self.mainPieChart delegate/ datasource to self already)
-(BOOL)pointingDeviceDraggedEvent:(id)event atPoint:(CGPoint)interactionPoint{
(after further testing)
Interesting, after trying to print out different values, by the shouldHandlePointingDevice function (simply clicking), I think i got some ideas now.
the self.mainPieChart.centerAnchor.x / y values always return 0.5 (both)
However, point x, point y are returning values vary from 1-500+,
it seems more like I am comparing two things, though they are on top of each other, from different perspective.
Likely the PlotSpace set delegate part messed that up.
============================================================
So, as for now I still don't know how to call -(BOOL)pointingDeviceDraggedEvent:(id)event atPoint:(CGPoint)interactionPoint{, I tried to put it into a if loop like
if([self.mainPieChart pointingDeviceDownEvent:event atPoint:self.mainPieChart.centerAnchor] == YES)
under my touched function but nothing happened, never mind.
Back to the point, my current solution works well now, even after applying padding.
float x = (self.hostView.bounds.size.width + self.hostView.hostedGraph.paddingLeft)*self.mainPieChart.centerAnchor.x;
float y = self.hostView.bounds.size.height * self.mainPieChart.centerAnchor.y;
float dx = point.x - x;
float dy = point.y - y;
double a = atan2(dx,dy);
these lines are all same for both press / drag functions, as for drag function,
float angleDifference = deltaAngle - a;
self.mainPieChart.startAngle = angleDifference;
are added before the end
However, the case is slightly different when the Pie Chart is not at the middle, or, in other words, the graph holding the Pie Chart is padded.
( my example somehow is mid centre just to make it easy)
you simply have to mortify the x y float value above, it's easier than I expected.
For example if I have,
graph.paddingLeft = -300.0f;
the value of float x in both press/drag will become
float x = (self.hostView.bounds.size.width + self.hostView.hostedGraph.paddingLeft)*self.mainPieChart.centerAnchor.x;
The pie chart centerAnchor is given as fractions of the width and height. Be sure to multiply the anchor values by the corresponding dimension of the graph before computing dx and dy.

How to support larger tiles

I've been working on a small platformer and decided to try new collision detection. I followed ray wenderlich's tutorial on how to make an iOS platformer and a couple of questions came up. In the tutorial its set up to support a very specific tile size and I was wondering how to modify it correctly to support a tile size of 80x80. This was the method used to get the tile coordinates and bounding boxes.
- (CGPoint)tileCoordForPosition:(CGPoint)position
{
float x = floor(position.x / map.tileSize.width);
float levelHeightInPixels = map.mapSize.height * map.tileSize.height;
float y = floor((levelHeightInPixels - position.y) / map.tileSize.height);
return ccp(x, y);
}
-(CGRect)tileRectFromTileCoords:(CGPoint)tileCoords
{
float levelHeightInPixels = map.mapSize.height * map.tileSize.height;
CGPoint origin = ccp(tileCoords.x * map.tileSize.width, levelHeightInPixels - ((tileCoords.y + 1) * map.tileSize.height));
return CGRectMake(origin.x, origin.y, map.tileSize.width, map.tileSize.height);
}
Since this code uses the values from the tilemap, if setup correctly in the tilemap you don't need to change the code.
I certainly don't see any magic numbers, which is good. Only fools and wizards use magic numbers. :)

OpenCV: How to get corners of CvBox2D?

I need to find corner positions of CvBox2D (or MCvBox2D) to map found contours on game object in XNA. I have a problem with correct translation of rotation angle. I thought that this is kind of basic operation but I kind find any solution in Internet.
I tried:
rotationAngle = box.angle * (180.0/ CV_PI);
angle = box.angle;
box.angle=rotationAngle;
alien.X = box.center.X - box.Width / 2;
alien.Y = box.center.Y - box.Height / 2;
alien.angle=angle;
but it wasn't translating it correctly.
Had someone ever tried to get corners on this kind of structure?
In EmguCV you just need to call
PointF[] corners = box.GetVertices();
if box is a MCvBox2D.
The simplest way to get the vertices of a CvBox2D is to convert it to a RotatedRect:
CvBox2D box = ...
cv::RotatedRect rr(box);
cv::Point2f vertices[4];
rr.points(vertices);
// vertices now has the four corners your seek

Rotating a Cocos2d sprite to match a joystick

I'm using a SneakyJoystick in Cocos2d, and I'm trying to get a sprite to rotate to face the same direction as the joystick is pointed (this is top down).
I can get it to rotate to face it, but it snaps into position as the rotation is updated every frame or so.
How can I make the sprite rotate smoothly towards the target angle, without jumping to it? I wasn't able to figure out how to do this with a CCRotateTo because the angle to rotate towards could change at any time.
I ended up fixing this simply by using a rotation method that I made, which rotates the node/sprite in the correct direction at the correct amount each update.
- (void)rotateNode:(CCNode*)aNode degrees:(float)targetRotation withSpeed:(float)rotationSpeed withTimeDelta:(ccTime)dt
{
rotationSpeed = rotationSpeed * dt;
// Convert the difference between the two angles to radians
float diff = (targetRotation - aNode.rotation) * (M_PI / 180);
// Find the rotation of the vector created by the sin and cos of the difference
float rotationDifference = atan2f(sinf(diff),cosf(diff));
// Rotate the clip accordingly
aNode.rotation += MAX(
MIN(
(180/M_PI) * rotationDifference,rotationSpeed), -rotationSpeed
);
}
Have you tried:
[CCRotateBy actionWithDuration:0.5f angle:CC_DEGREES_TO_RADIANS(90.0f)];
Obtain the Angle between the last Update the current Update, also if you wanted it so the character had a set time to turn around, you can scale your duration by the amount of the angle.

Resources