I am interested in capturing a portion of the map to create the image you see on the top of the popover. I guess I'll capture a UIImage, you can do so starting from the coordinates of the pin?
Thanks
You can try something like this:
- (UIImage*) imageFromView:(UIView*)view rect:(CGRect)rect {
// capture the full view in an image
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage* viewImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// crop down to just our desired rectangle, accounting for Retina scale
CGFloat scale = [[UIScreen mainScreen] scale];
CGRect scaledRect = CGRectMake(scale * rect.origin.x, scale * rect.origin.y,
scale * rect.size.width, scale * rect.size.height);
CGImageRef resultImgRef = CGImageCreateWithImageInRect([viewImg CGImage], scaledRect);
UIImage* result = [UIImage imageWithCGImage: resultImgRef
scale: 1.0f / scale
orientation: UIImageOrientationUp];
CGImageRelease(resultImgRef);
return result;
}
- (IBAction)onButtonTapped:(id)sender {
// just pick the map's center as the location to capture. could be anything.
CLLocationCoordinate2D center = self.map.centerCoordinate;
// convert geo coordinates to screen (view) coordinates
CGPoint point = [self.map convertCoordinate:center toPointToView: self.map];
// make this span whatever you want - I use 120x80 points
float width = 120.0;
float height = 80.0;
// here's the frame in which we'll capture the underlying map
CGRect frame = CGRectMake(point.x - width / 2.0,
point.y - height / 2.0,
width, height);
// just show the captured image in a UIImageView overlay
// in the top, left corner, with 1:1 scale
UIImageView* overlay = [[UIImageView alloc] initWithImage: [self imageFromView: self.map rect: frame]];
frame.origin = CGPointZero;
overlay.frame = frame;
[self.view addSubview: overlay];
}
The imageFromView:rect: method just captures a given rectangular area, within a given view, and produces a UIImage.
The onButtonTapped method uses the first method to capture an area around the center of my map, and display the captured image at the upper left corner of the screen. Of course, you can replace the map center with the coordinate of your pin, make the area width/height whatever you like, and put the resulting image into your popup view.
This is just a demonstration. I used a button in my sample app, just to trigger the capture, after panning the map where I wanted it to be.
Results
Limitations
My code just displays the captured rectangle at a 1:1 size ratio. Of course, if you want, you can put the captured UIImage into a UIImageView that scales it however you like. You could make the image bigger. But, you will lose image sharpness if you do that. This process just does a screen capture of a UIView. It doesn't work on map data directly, so when you blow it up (display the image at a larger size), the image doesn't sharpen as it does when you actually zoom in on a MKMapView.
On iOS7 you can use MKMapSnapshotter
Related
I am going through 19th chapter of Big Nerd Ranch, iOS textbook and can not understand several parts of the function that takes in a big image and creates a thumbnail out of it. Have a look:
- (void)setThumbnailFromImage:(UIImage *)image
{
CGSize origImageSize = image.size;
// The rectangle of the thumbnail
CGRect newRect = CGRectMake(0, 0, 40, 40);
// Figure out a scaling ratio to make sure we maintain the same aspect ratio
float ratio = MAX(newRect.size.width / origImageSize.width,
newRect.size.height / origImageSize.height);
// Create a transparent bitmap context with a scaling factor
// equal to that of the screen
UIGraphicsBeginImageContextWithOptions(newRect.size, NO, 0.0);
// Create a path that is a rounded rectangle
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:newRect
cornerRadius:5.0];
// Make all subsequent drawing clip to this rounded rectangle
[path addClip];
// Center the image in the thumbnail rectangle
CGRect projectRect;
projectRect.size.width = ratio * origImageSize.width;
projectRect.size.height = ratio * origImageSize.height;
projectRect.origin.x = (newRect.size.width - projectRect.size.width) / 2.0;
projectRect.origin.y = (newRect.size.height - projectRect.size.height) / 2.0;
[image drawInRect:projectRect];
// Get the image from the image context; keep it as our thumbnail
UIImage *smallImage = UIGraphicsGetImageFromCurrentImageContext();
self.thumbnail = smallImage;
// Cleanup image context resources; we're done
UIGraphicsEndImageContext();
}
From my understanding we are getting the MAX of the two ratios and then we put a smaller edge of the original image equal to newRect's edge (which is 40 in our case), the other edge seemingly should stick out of the newRect since the edge would be larger than the edge of newRect, when we UIGraphicsGetImageFromCurrentImageContext(). That's my vague 'understanding'.
Could anyone please explain what is this whole code doing in a detailed way, especially, the centering part? If you know some tutorials that might be relevant, it would also be great.
I just took or added to the previous comments and tried to explain each part more clearly. You seemed to get the basic idea, so I hope this helps solidify everything.
- (void)setThumbnailFromImage:(UIImage *)image
{
CGSize origImageSize = image.size;
//Create new rectangle of your desired size
CGRect newRect = CGRectMake(0, 0, 40, 40);
//Divide both the width and the height by the width and height of the original image to get the proper ratio.
//Take whichever one is greater so that the converted image isn't distorted through incorrect scaling.
float ratio = MAX(newRect.size.width / origImageSize.width,
newRect.size.height / origImageSize.height);
// Create a transparent bitmap context with a scaling factor
// equal to that of the screen
// Basically everything within this builds the image
UIGraphicsBeginImageContextWithOptions(newRect.size, NO, 0.0);
// Create a path that is a rounded rectangle -- essentially a frame for the new image
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:newRect
cornerRadius:5.0];
// Applying path
[path addClip];
// Center the image in the thumbnail rectangle
CGRect projectRect;
// Scale the image with previously determined ratio
projectRect.size.width = ratio * origImageSize.width;
projectRect.size.height = ratio * origImageSize.height;
// I believe the anchor point of the new image is (0.5, 0.5), so here he is setting the position to be in the middle
// Half of the width and height added to whatever origin you have (in this case 0) will give the proper coordinates
projectRect.origin.x = (newRect.size.width - projectRect.size.width) / 2.0;
projectRect.origin.y = (newRect.size.height - projectRect.size.height) / 2.0;
// Add the scaled image
[image drawInRect:projectRect];
// Retrieving the image that has been created and saving it in memory
UIImage *smallImage = UIGraphicsGetImageFromCurrentImageContext();
self.thumbnail = smallImage;
// Cleanup image context resources; we're done
UIGraphicsEndImageContext();
}
I want to draw a image at angle. I have one ImageView with contect mode Aspect to fit. Also I have one dragView which user can move and rotate on the View. I have moved the dragView with CGAffineTransformMakeRotation
Both the ImageView and dragView is added on the main (self) view.
I have done the following code.
It has two problem
I dont know how to save image at angle that i can get from the CGAffineTransformMakeRotation
dragView is not placed currently on the image. it has some variation in the x and y position.
-(void)btnSaveClicked{
UIGraphicsBeginImageContextWithOptions(imgView.image.size, NO, 0.0); // create context as the image size
UIImage *dragImage = [self imageWithView:dragView]; // convert image from the dragView
[imgView.image drawInRect:CGRectMake(0, 0, imgView.image.size.width, imgView.image.size.height)]; // draw main image
CGSize size = imageDraw.size;
float factor = imgView.image.size.width / imgView.frame.size.width;
if (factor < imgView.image.size.height / imgView.frame.size.height) {
factor = imgView.image.size.height / imgView.frame.size.height;
}
CGRect rect = CGRectMake(self.view.frame.size.width/2-size.width/factor/2, self.view.frame.size.height/2-size.height/factor/2, size.width/factor, size.height/factor);
CGPoint point = CGPointMake((dragView.frame.origin.x+10-rect.origin.x)*factor,(dragView.frame.origin.y+10 - rect.origin.y)*factor);
[dragImage drawAtPoint:point blendMode:kCGBlendModeNormal alpha:1.0]; // draw the dragImnage but the point is wrong
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
imgView.image = newImage;
}
Sample Project : https://github.com/SunnyShah407/WaterMark/tree/master
My task is to get the images from photo album and show it in one ViewController which has one UIImageView in full screen. The problem am facing here is while showing the small size photos captured in camera are getting stretched when it comes to UIImageView
I have tried with all aspect mode for keeping the image as normal. but no luck
Thanks in advance!!
If you want the image selected from Photo Album with same size then
self.photoImgView.contentMode=UIViewContentModeScaleAspectFit;// is best
self.photoImgView.contentMode=UIViewContentModeScaleAspectFill; //if you want to fill the large imageview size with selected photo
self.photoImgView.contentMode=UIViewContentModeScaleToFill; // if you want to fill the large imageview size with selected photo
Else you can try with below solution for resizing image before assigning to imageview.
UIImage *uiImage=[info valueForKey:#"UIImagePickerControllerOrignalImage"];//UIImagePickerControllerEditedImage
CGSize size;
NSData *imageData;
imageData= UIImageJPEGRepresentation(uiImage, 1.0);
UIImage *galleryImage=[self squareImageWithImage:[UIImage imageWithData:imageData] scaledToSize:CGSizeMake(320, 320)];
photoImgView.image=galleryImage;
-(UIImage *)squareImageWithImage:(UIImage *)image scaledToSize:(CGSize)newSize {
double ratio;
double delta;
CGPoint offset;
//make a new square size, that is the resized imaged width
CGSize sz = CGSizeMake(newSize.width, newSize.width);
//figure out if the picture is landscape or portrait, then
//calculate scale factor and offset
if (image.size.width > image.size.height) {
ratio = newSize.width / image.size.width;
delta = (ratio*image.size.width - ratio*image.size.height);
offset = CGPointMake(delta/2, 0);
} else {
ratio = newSize.width / image.size.height;
delta = (ratio*image.size.height - ratio*image.size.width);
offset = CGPointMake(0, delta/2);
}
//make the final clipping rect based on the calculated values
CGRect clipRect = CGRectMake(-offset.x, -offset.y,
(ratio * image.size.width) + delta,
(ratio * image.size.height) + delta);
//start a new context, with scale factor 0.0 so retina displays get
//high quality image
if ([[UIScreen mainScreen] respondsToSelector:#selector(scale)]) {
UIGraphicsBeginImageContextWithOptions(sz, YES, 0.0);
} else {
UIGraphicsBeginImageContext(sz);
}
UIRectClip(clipRect);
[image drawInRect:clipRect];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
Hope it helps you..
Set the content mode of your image view. Try this:
imageView.contentMode = UIViewContentModeScaleAspectFit;
Hope this helps.. :)
I assume when you say
"I have tried with all aspect mode for keeping the image as normal. but no luck"
You mean you used the contentMode or UIImageView?
e.g.
self.myImageView.contentMode = UIViewContentModeScaleAspectFit;
or one of the others AspectFill is another common one fit letterboxes I think and fill scales past the edge until the entire screen is filled.
But if your image is too small then it is going to look stretched even if the aspect ratio is correct, as there just wont be enough data for a full image
I would use:
UIImageView *someImage = [UIImageView new];
someImage.contentMode = UIViewContentModeScaleAspectFill;
someImage.clipsToBounds = YES;
I think I may have an odd request, however hopefully someone can help. I am using the well known UIScrollView + UIImageView to zoom into and out of an image, as well as pan. This works fine and dandy, but the current project we have needs to be able to crop the image, but also include the black bars on the sides if the image is smaller than the crop rectangle. See the images below.
We wish to capture everything inside of the blue box, including the white (which will be black, since opaque is set to YES).
This works great for images that are completely zoomed out (The white is just the UIImageView's extra space).
However the problem arises when we try to zoom into the image, and capture only that portion, plus the empty space.
This results in the following image
The problem we are seeing is we need to be able to create an image that is exactly what is in the Crop Rect, regardless if there is part of the image there or not. The other problem is we wish to have the ability to dynamically change the output resolution. The aspect ratio is 16:9, and for this example kMaxWidth = 1136 and kMaxHeight = 639, however in the future we may want to request a larger or smaller 16:9 resolution.
Below is the function I have so far:
- (UIImage *)createCroppedImageFromImage:(UIImage *)image {
CGSize newRect = CGSizeMake(kMaxWidth, kMaxHeight);
UIGraphicsBeginImageContextWithOptions(newRect, YES, 0.0);
// 0 is the edge of the screen, to help with zooming
CGFloat xDisplacement = ((abs(0 - imageView.frame.origin.x) * kMaxWidth) / (self.cropSize.width / self.scrollView.zoomScale) / self.scrollView.zoomScale);
CGFloat yDisplacement = ((abs(self.cropImageView.frame.origin.y - imageView.frame.origin.y) * kMaxHeight) / (self.cropSize.height / self.scrollView.zoomScale) / self.scrollView.zoomScale);
CGFloat newImageWidth = (self.image.size.width * kMaxWidth) / (self.cropSize.width / self.scrollView.zoomScale);
CGFloat newImageHeight = (self.image.size.height * kMaxHeight) / (self.cropSize.height / self.scrollView.zoomScale);
[image drawInRect:CGRectMake(xDisplacement, 0, newImageWidth, newImageHeight)];
UIImage *croppedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return croppedImage;
}
Any help would be greatly appreciated.
I ended up just taking a screenshot, and cropping that. It seems to work well enough.
- (UIImage *)cropImage {
CGRect cropRect = self.cropOverlay.cropRect;
UIGraphicsBeginImageContext(self.view.frame.size);
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *fullScreenshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGImageRef croppedImage = CGImageCreateWithImageInRect(fullScreenshot.CGImage, cropRect);
UIImage *crop = [[UIImage imageWithCGImage:croppedImage] resizedImage:self.outputSize interpolationQuality:kCGInterpolationHigh];
CGImageRelease(croppedImage);
return crop;
}
If using iOS 7, you would use drawViewHierarchyInRect:afterScreenUpdates:, instead of renderInContext:
I think the translated rect for the image view isn't calculated properly. Since UIImageView is the subview inside the UIScrollView, you should be able to calculate the visible rect by calling [scrollView convertRect:scrollView.bounds toView:imageView];. That will be the visible rect of your image view. All you need to now is crop it.
-(UIImage*)cropImage:(UIImage*)img inRect:(CGRect)rect{
CGImageRef cropped = CGImageCreateWithImageInRect(img.CGImage, rect);
UIImage *image = [UIImage imageWithCGImage:cropped];
CGImageRelease(cropped);
return image;
}
Edit: Yeah... I forgot to mention that cropping should be done in (0,1) coordinate space. I've modified the crop function for you, so it crops the image based on all parameters you provided, UIImageView inside UIScrollView and an image.
-(UIImage*)cropImage:(UIImage*)image inImageView:(UIImageView*)imageView scrollView:(UIScrollView*)scrollView{
// get visible rect from image scrollview
CGRect visibleRect = [scrollView convertRect:scrollView.bounds toView:imageView];
UIImage* rCroppedImage;
CALayer* maskLayer= [[CALayer alloc] init];
maskLayer.contents= (id)image.CGImage;
maskLayer.frame= CGRectMake(0, 0, visibleRect.size.width, visibleRect.size.height);
CGRect rect= CGRectMake(visibleRect.origin.x / image.size.width,
visibleRect.origin.y / image.size.height,
visibleRect.size.width / image.size.width,
visibleRect.size.height / image.size.height);
maskLayer.contentsRect= rect;
UIGraphicsBeginImageContext(visibleRect.size);
CGContextRef context= UIGraphicsGetCurrentContext();
[maskLayer renderInContext:context];
rCroppedImage= UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return rCroppedImage;
}
There are several post that I've found but none of them are useful for me.
I want to rotate a image (either clockwise/anti-clockwise at a time). I've done this by following code but when I assign a rotated image to a image view then image become smaller after every click.
I've debugged and found that at every rotation (either clockwise/anti-clockwise) image size is increased. I know when image is rotated then image size is little bit increased but here image size is increased much grater than expectation.
//code for image rotation
- (UIImage *)imageRotatedByDegrees:(UIImage*)oldImage deg:(CGFloat)degrees{
// calculate the size of the rotated view's containing box for our drawing space
UIView *rotatedViewBox = [[UIView alloc] initWithFrame:CGRectMake(0,0,oldImage.size.width, oldImage.size.height)];
CGAffineTransform t = CGAffineTransformMakeRotation(degrees * M_PI / 180);
rotatedViewBox.transform = t;
CGSize rotatedSize = rotatedViewBox.frame.size;
// Create the bitmap context
UIGraphicsBeginImageContext(rotatedSize);
CGContextRef bitmap = UIGraphicsGetCurrentContext();
// Move the origin to the middle of the image so we will rotate and scale around the center.
CGContextTranslateCTM(bitmap, rotatedSize.width/2, rotatedSize.height/2);
// // Rotate the image context
CGContextRotateCTM(bitmap, (degrees * M_PI / 180));
// Now, draw the rotated/scaled image into the context
CGContextScaleCTM(bitmap, 1.0, -1.0);
CGContextDrawImage(bitmap, CGRectMake(-oldImage.size.width / 2, -oldImage.size.height / 2, oldImage.size.width, oldImage.size.height), [oldImage CGImage]);
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
- (IBAction)btnRotateImageTapped:(UIButton *)sender {
static NSInteger degree = 0;
if (sender.tag == 471) { //rotate left btn tag
degree += 1;
} else if (sender.tag == 472) { //rotate right btn tag
degree += -1;
}
UIImage *img = [self imageRotatedByDegrees:self.imgViewTeethTemplate.image deg:degree];
self.imgViewTeethTemplate.image = img;
}
I don't know is this right way or not. If not can anyone help me out of this. Any help is must appreciated.
I think it is because you are calculating the new size at each rotation. You should copy your image into a new one that is 1/cos(45deg) larger, (that is 2/sqrt(2)) and then do the rotations of this new image as required. You do not need to worry about loosing part of your original image as it will be contained in the larger one whatever the rotation is.