How do I rotate an image from a file with Xamarin on iOS - ios

I have been trying to rotate an image for a couple days now, but the best I get is still a black image.
I suspect it may have something to do with the point I'm rotating around but I'm not sure. I say that because I tried the whole solution proposed here and translated in Xamarin terms, but that didn't work.
Here's my code:
public void Rotate (string sourceFile, bool isCCW){
using (UIImage sourceImage = UIImage.FromFile(sourceFile))
{
var sourceSize = sourceImage.Size;
UIGraphics.BeginImageContextWithOptions(new CGSize(sourceSize.Height, sourceSize.Width), true, 1.0f);
CGContext bitmap = UIGraphics.GetCurrentContext();
// rotating before DrawImage didn't work, just got the image cropped inside a rotated frame
// bitmap.RotateCTM((float)(isCCW ? Math.PI / 2 : -Math.PI / 2));
// swapped Width and Height because the image is rotated
bitmap.DrawImage(new CGRect(0, 0, sourceSize.Height, sourceSize.Width), sourceImage.CGImage);
// rotating after causes the resulting image to be just black
bitmap.RotateCTM((float)(isCCW ? Math.PI / 2 : -Math.PI / 2));
var resultImage = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
if (targetFile.ToLower().EndsWith("png"))
resultImage.AsPNG().Save(sourceFile, true);
else
resultImage.AsJPEG().Save(sourceFile, true);
}
}

It looks like you want to take a UIImage, and then rotate it either 90 clockwise or 90 degrees counter clockwise. You can actually do this with just a few lines of code:
public void RotateImage(ref UIImage imageToRotate, bool isCCW)
{
var imageRotation = isCCW ? UIImageOrientation.Right : UIImageOrientation.Left;
imageToRotate = UIImage.FromImage(imageToRotate.CGImage, imageToRotate.CurrentScale, imageRotation);
}
We use UIImage.FromImage() that accepts 3 parameters. The first is a CGImage, from which we can get from the UIImage you're trying to rotate. The 2nd parameter is the scale of the image. The 3rd parameter is the important one. We can rotate it using UIImageOrientation.Right (90 degrees CCW) or UIImageOrientation.Left (90 degrees CW). You can check out the Apple documentation for the meaning of the other UIImageOrientation constants:
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIImage_Class/index.html#//apple_ref/c/tdef/UIImageOrientation
UPDATE:
Note that the code above only changes EXIF flags and calling it twice doesn't rotate the image 180deg.
Add this code to make the result cumulative:
UIGraphics.BeginImageContextWithOptions(new CGSize((float)h, (float)w), true, 1.0f);
imageToRotate.Draw(new CGRect(0, 0, (float)h, (float)w));
var resultImage = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
imageToRotate = resultImage;

Related

Cropping UIImage Using VNRectangleObservation

I'm using the Vision framework to detect rectangular documents in a captured photo. Detecting and drawing a path around the document is working perfectly. I then want to crop the image to be only the detected document. I'm successfully cropping the image, but it seems the coordinates don't line up and the cropped image is only part of the detected document and the rest is just the desk behind the document. I'm using the following cropping code:
private UIImage CropImage(UIImage image, CGRect rect, float scale)
{
var drawRect = new CGRect(rect.X, rect.Y, rect.Size.Width, rect.Size.Height);
using (var cgImage = image.CGImage.WithImageInRect(drawRect))
{
var croppedImage = UIImage.FromImage(cgImage);
return croppedImage;
};
}
Using the following parameters:
image is the same UIImage that i successfully drew the rectangle path on.
rect is the VNRectangleObservation.BoundingBox. This is normalized so i'm scaling it using the image.size. it's the same scaling i do when drawing the rectangle path.
scale is 1f, but i'm currently ignoring this.
The cropped image generally seems to be the right size, but it is shifted up and to the left which cuts off the lower and right side of the document. Any help would be appreciated.
for anyone else that finds this, the issue seemed to be CGImage rotating when cropping the image which caused the VNRectangleObservation to not line up anymore. I used this article, Tracking and Altering Images, to get a working solution using CIFilter. Cropping code follows:
var ciFilter = CIFilter.FromName("CIPerspectiveCorrection");
if (ciFilter == null) continue;
var width = inputImage.Extent.Width;
var height = inputImage.Extent.Height;
var topLeft = new CGPoint(observation.TopLeft.X * width, observation.TopLeft.Y * height);
var topRight = new CGPoint(observation.TopRight.X * width, observation.TopRight.Y * height);
var bottomLeft = new CGPoint(observation.BottomLeft.X * width, observation.BottomLeft.Y * height);
var bottomRight = new CGPoint(observation.BottomRight.X * width, observation.BottomRight.Y * height);
ciFilter.SetValueForKey(new CIVector(topLeft), new NSString("inputTopLeft"));
ciFilter.SetValueForKey(new CIVector(topRight), new NSString("inputTopRight"));
ciFilter.SetValueForKey(new CIVector(bottomLeft), new NSString("inputBottomLeft"));
ciFilter.SetValueForKey(new CIVector(bottomRight), new NSString("inputBottomRight"));
var ciImage = inputImage.CreateByApplyingOrientation(CGImagePropertyOrientation.Up);
ciFilter.SetValueForKey(ciImage, CIFilterInputKey.Image);
var outputImage = ciFilter.OutputImage;
var uiImage = new UIImage(outputImage);
imageList.Add(uiImage);
imageList is a List<UImage> since i'm handling multiple detected rectangles.
observation is a single observation of type VNRectangleObservation.
The cropped image generally seems to be the right size, but it is shifted up and to the left which cuts off the lower and right side of the document.
From Apple documentation CGImageCreateWithImageInRect , there is a discussion about the cropped size .
CGImageCreateWithImageInRect performs the following tasks to create the subimage:
It calls the CGRectIntegral function to adjust the rect parameter to integral bounds.
It intersects the rect with a rectangle whose origin is (0,0) and size is equal to the size of the image specified by the image parameter.
It reads the pixels within the resulting rectangle, treating the first pixel within as the origin of the subimage.
If W and H are the width and height of image, respectively, then the point (0,0) corresponds to the first pixel of the image data. The point (W–1, 0) is the last pixel of the first row of the image data, while (0, H–1) is the first pixel of the last row of the image data and (W–1, H–1) is the last pixel of the last row of the image data.
Then you can check in your local project with an image (size is : 1920 * 1080) as follow:
UIImageView imageView = new UIImageView(new CGRect(0, 400, UIScreen.MainScreen.Bounds.Size.Width, 300));
UIImage image = new UIImage("th.jpg");
imageView.Image = CropImage(image, new CGRect(0, 0, 1920, 1080), 1);
View.AddSubview(imageView);
The CropImage Method :
private UIImage CropImage(UIImage image, CGRect rect, float scale)
{
var drawRect = new CGRect(rect.X, rect.Y, rect.Size.Width, rect.Size.Height);
using (var cgImage = image.CGImage.WithImageInRect(drawRect))
{
if(null != cgImage)
{
var croppedImage = UIImage.FromImage(cgImage);
return croppedImage;
}
else
{
return image;
}
};
}
This will show the Original Size of Image :
Now you can modify the cropped size as follow :
UIImageView imageView = new UIImageView(new CGRect(0, 400, UIScreen.MainScreen.Bounds.Size.Width, 300));
UIImage image = new UIImage("th.jpg");
imageView.Image = CropImage(image, new CGRect(0, 0, 1920, 100), 1);
View.AddSubview(imageView);
Here I set x = 0 , y = 0 , that means from (0,0) to start , and width is 1920 ,height is 100 . I just crop the height of the original Image . The effect as follow :
Then if you modify the x/y ,the cropped image will move to other area to crop .As follow:
UIImageView imageView = new UIImageView(new CGRect(0, 400, UIScreen.MainScreen.Bounds.Size.Width, 300));
UIImage image = new UIImage("th.jpg");
imageView.Image = CropImage(image, new CGRect(0, 100, 1920, 100), 1);
View.AddSubview(imageView);
Then you will see it's different with the second effect :
So when cropping an image , you should understand the drawRect of image.CGImage.WithImageInRect(drawRect) clearly .
Note from doc :
Be sure to specify the subrectangle's coordinates relative to the original image's full size, even if the UIImageView shows only a scaled version.

Fade UICollectionViewCells with overlap goes wrong

I have an UICollectionView with some UICollectionViewCells. Cells are supposed to overlap each other, but also to fade a bit based on their position. See below the result:
How can I avoid those corners to be visible? (top between 3 and 4, or 4 and 5, or all the right side between 5 and 6). They should overlap, but that should not affect the image.
In order to create a fade effect I would use an overlay like this:
Save the original image in a variable to be able to reset the process for different alpha values
Draw a shape that has same color as background (color alpha should be proportional with the item position) on top of your current image
Replace the result image with your current one
I will give you an example to illustrate better:
private UIImage baseImage;
private UIImage ChangeImageColor(UIImage image, nfloat alpha, UIColor color)
{
var alphaColor = color.ColorWithAlpha(alpha);
if(baseImage == null)
{
baseImage = image;
}
else
{
image = baseImage;
}
UIGraphics.BeginImageContextWithOptions(image.Size, false, UISCreen.MainScreen.Scale);
var context = UIGraphics.GetCurrentContext();
alphaColor.SetFill();
context.TranslateCTM(0, image.Size.Height);
context.ScaleCTM(new nfloat(1.0), new nfloat(-1.0));
context.SetBlendMode(CGBlendMode.Lighten);
var rect = new CGRect(0, 0, image.Size.Width, image.Size.Height);
context.DrawImage(rect, image.CGImage);
context.SetBlendMode(CGBlendMode.SourceAtop);
context.AddRect(rect);
context.DrawPath(CGPathDrawingMode.Fill);
image = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
return image;
}

Cropping CIImage

I have a class that takes an UIImage, initializes a CIImage with it like so:
workingImage = CIImage.init(image: baseImage!)
Then the image is used to cut out 9 neighbouring squares in a 3x3 pattern out of it - in a loop:
for x in 0..<3
{
for y in 0..<3
{
croppingRect = CGRect(x: CGFloat(Double(x) * sideLength + startPointX),
y: CGFloat(Double(y) * sideLength + startPointY),
width: CGFloat(sideLength),
height: CGFloat(sideLength))
let tmpImg = (workingImage?.cropping(to: croppingRect))!
}
}
Those tmpImgs are inserted into a table and later used, but thats besides the point.
This code works on IOS 9, and on IOS 10 simulators, but not on an actual IOS 10 device. The images produced are either all empty, or one of them is like a half of what its supposed to be, with the rest being, again, empty.
Is this not how its supposed to be done in IOS 10?
The heart of the matter is that passing through CIImage is not the way to crop a UIImage. For one thing, coming back from CIImage to UIImage is a complicated business. For another, the whole round-trip is unnecessary.
How To Crop
To crop an image, make an image graphics context of the desired cropped size and call draw(at:) on the UIImage to draw it at the desired point relative to the graphics context, so that the desired portion of the image falls into the context. Now extract the resulting new image and close the context.
To demonstrate, I'll crop to one of the thirds you are trying to crop to, namely the lower right third:
let sz = baseImage.size
UIGraphicsBeginImageContextWithOptions(
CGSize(width:sz.width/3.0, height:sz.height/3.0),
false, 0)
baseImage.draw(at:CGPoint(x: -sz.width/3.0*2.0, y: -sz.height/3.0*2.0))
let tmpImg = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
Original image (baseImage):
Cropped image (tmpImg):
The other sections are completely parallel.
Core Image's coordinate system mismatches with UIKit, so the rect needs to be mirrored.
So in your specific case, you want:
var ciRect = croppingRect
ciRect.origin.y = workingImage!.extent.height - ciRect.origin.y - ciRect.height
let tmpImg = workingImage!.cropped(to: ciRect)
This definitely works for iOS 10+.
In a more general case, we would make a UIImage extension that covers both possible coordinate systems, and that's way faster than draw(at:):
extension UIImage {
/// Return a new image cropped to a rectangle.
/// - parameter rect:
/// The rectangle to crop.
open func cropped(to rect: CGRect) -> UIImage {
// a UIImage is either initialized using a CGImage, a CIImage, or nothing
if let cgImage = self.cgImage {
// CGImage.cropping(to:) is magnitudes faster than UIImage.draw(at:)
if let cgCroppedImage = cgImage.cropping(to: rect) {
return UIImage(cgImage: cgCroppedImage)
} else {
return UIImage()
}
}
if let ciImage = self.ciImage {
// Core Image's coordinate system mismatch with UIKit, so rect needs to be mirrored.
var ciRect = rect
ciRect.origin.y = ciImage.extent.height - ciRect.origin.y - ciRect.height
let ciCroppedImage = ciImage.cropped(to: ciRect)
return UIImage(ciImage: ciCroppedImage)
}
return self
}
}
I've made a pod for it, so the source code is at https://github.com/Coeur/ImageEffects/blob/master/SwiftImageEffects/ImageEffects%2Bextensions.swift

iOS: Swift: How to get proper image quality with CGImageCreateWithImageInRect?

I am trying to make a simple Crop functionality with Swift. I am trying with CGImageCreateWithImageInRect function - which works perfectly but produce inferior quality. Am I missing something ?
func retriveCroppedImage(){
let yratio: CGFloat = imgviewrect.size.height / chosenImage.size.height
let xratio: CGFloat = imgviewrect.size.width / chosenImage.size.width
var cliprect = CGRectMake(centerpoint.x - vWidth/2, centerpoint.y - vHeight/2, vWidth, vHeight)
print("cliprect top \(cliprect.size)")
cliprect.size.height = cliprect.size.height / xratio;
cliprect.size.width = cliprect.size.width / xratio;
cliprect.origin.x = cliprect.origin.x / xratio + imgviewrect.origin.x / xratio
cliprect.origin.y = cliprect.origin.y / yratio - imgviewrect.origin.y / xratio
print("cliprect On Image \(cliprect)")
let imageRef = CGImageCreateWithImageInRect(chosenImage.CGImage, cliprect )
croppedImg = UIImage(CGImage: imageRef!, scale: UIScreen.mainScreen().scale, orientation: chosenImage.imageOrientation)
print("Operation complete");
}
Screen shots : Main VC
after cropping I get Cropped Image
After trying all the options - I found accidentally I set Alpha in Image View on the story board. There was nothing wrong with the CGImageCreateWithImageInRect function. Now my cropping app is working as desired. But thank you all for the suggestions.

IOS drawing a simple image turns out blurry using Xamarin C#

I'm developing in Xamarin and would like to draw a simple circle with the text "CIRCLE" inside it and display that image in a UIImageView. The problem is that the circle and text appear very blurry. I've read a bit about subpixels but I don't think that's my problem.
Here's the blurry image and code, hoping someone has some ideas :)
UIGraphics.BeginImageContext (new SizeF(150,150));
var context = UIGraphics.GetCurrentContext ();
var content = "CIRCLE";
var font = UIFont.SystemFontOfSize (16);
const float width = 150;
const float height = 150;
context.SetFillColorWithColor (UIColor.Red.CGColor);
context.FillEllipseInRect (new RectangleF (0, 0, width, height));
var contentString = new NSString (content);
var contentSize = contentString.StringSize (font);
var rect = new RectangleF (0, ((height - contentSize.Height) / 2) + 0, width, contentSize.Height);
context.SetFillColorWithColor (UIColor.White.CGColor);
new NSString (content).DrawString (rect, font, UILineBreakMode.WordWrap, UITextAlignment.Center);
var image = UIGraphics.GetImageFromCurrentImageContext ();
imageView.Image = image;
The reason why it's blurry is because it was resized. That bitmap is not 150x150 (like the context you created), it's 333x317 pixels.
It could be because imageView is scaling it's image (there's no source code) or because of pixel doubling (for retina displays). In the later case what you really want to use is:
UIGraphics.BeginImageContextWithOptions (size, false, 0);
which will (using 0) automagically use the right scaling factor for retina (or not) display - and look crisp (and not oversized) on all types of devices.

Resources