Draw into UIImage - xamarin

how can I draw into an existing UIImage using monotouch?
I load an image: UIImage.FromFile("MyImage.png")
Then I want to draw a string and some lines into this image.
Does anyone has a code sample?
Thx

Here is a method that does it:
private void drawOnTouch(object data)
{
UITouch touch = data as UITouch;
if (null != touch)
{
UIGraphics.BeginImageContext(this.Image == null ? this.Frame.Size : this.Image.Size);
using (CGContext cont = UIGraphics.GetCurrentContext())
{
if (this.Image != null)
{
cont.TranslateCTM(0f, this.Image.Size.Height);
cont.ScaleCTM(1.0f, -1.0f);
cont.DrawImage(new RectangleF(0f,0f,this.Image.Size.Width, this.Image.Size.Height), this.Image.CGImage);
cont.ScaleCTM(1.0f, -1.0f);
cont.TranslateCTM(0f, -this.Image.Size.Height);
} //end if
PointF lastLocation = touch.PreviousLocationInView(this);
PointF pt = touch.LocationInView(this);
using (CGPath path = new CGPath())
{
cont.SetLineCap(CGLineCap.Round);
cont.SetLineWidth(3);
cont.SetRGBStrokeColor(0, 2, 3, 1);
path.MoveToPoint(lastLocation.X, lastLocation.Y);
path.AddLines(new PointF[] { new PointF(lastLocation.X,
lastLocation.Y),
new PointF(pt.X, pt.Y) });
path.CloseSubpath();
cont.AddPath(path);
cont.DrawPath(CGPathDrawingMode.FillStroke);
this.Image = UIGraphics.GetImageFromCurrentImageContext();
}//end using path
}//end using cont
UIGraphics.EndImageContext();
this.SetNeedsDisplay();
}//end if
}//end void drawOnTouch
If you place this method in a subclass of UIImageView and call it from TouchesBegan and TouchesMoved, when you touch the screen it will draw on the image.

Related

Is there an property like android:actionBarSize in iOS?

I'm developing in Xamarin and I don't know how change the height of navigation bar.
For android is easier because you've got a clear property "ActionBarSize"
I've tried a lot of things. Create a comun NavigationPage with HeightRequest property does not work
Thanks a lot
In iOS we could set a custom TitlView by using custom renderer .
using Foundation;
using UIKit;
using Xamarin.Forms.Platform.iOS;
using Xamarin.Forms;
using xxx.iOS;
using CoreGraphics;
using xxx;
using ObjCRuntime;
[assembly: ExportRenderer(typeof(ContentPage), typeof(MyPageRenderer))]
namespace xxx.iOS
{
public class MyPageRenderer: PageRenderer
{
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
var page = Element as ContentPage;
NavigationController.NavigationBar.Hidden = true;
double height = IsiphoneX();
UIView backView = new UIView()
{
BackgroundColor = UIColor.White,
Frame = new CGRect(0,20,UIScreen.MainScreen.Bounds.Width, height),
};
UIButton backBtn = new UIButton() {
Frame = new CGRect(20, height-44, 40, 44),
Font = UIFont.SystemFontOfSize(18),
} ;
backBtn.SetTitle("Back", UIControlState.Normal);
backBtn.SetTitleColor(UIColor.Blue, UIControlState.Normal);
backBtn.AddTarget(this,new Selector("GoBack"),UIControlEvent.TouchUpInside);
UILabel titleLabel = new UILabel() {
Frame=new CGRect(UIScreen.MainScreen.Bounds.Width/2-75, 0,150, height),
Font = UIFont.SystemFontOfSize(20),
Text = page.Title,
TextColor = UIColor.Black,
Lines = 0,
};
UILabel line = new UILabel() {
Frame = new CGRect(0, height, UIScreen.MainScreen.Bounds.Width, 0.5),
BackgroundColor = UIColor.Black,
};
backView.AddSubview(backBtn);
backView.AddSubview(titleLabel);
backView.AddSubview(line);
View.AddSubview(backView);
}
double IsiphoneX()
{
double height = 44; //set height as you want here !!!
if (UIDevice.CurrentDevice.CheckSystemVersion(11, 0))
{
if (UIApplication.SharedApplication.Delegate.GetWindow().SafeAreaInsets.Bottom > 0.0)
{
height = 64; //set height as you want here !!!
}
}
return height;
}
[Export("GoBack")]
void GoBack()
{
NavigationController.PopViewController(true);
}
public override void ViewWillDisappear(bool animated)
{
base.ViewWillDisappear(animated);
NavigationController.NavigationBar.Hidden = false;
}
}
}

Canvas drawImage scale height and width in CustomPainter

I am developing an application in Flutter where I am using CustomPainter to draw an image which the user picks from gallery/camera. In addition to this the use can draw lines as well as change the stroke value, opacity colour and colour on it its own. For this I have created 2 classes DrawEditor and DrawingPainter the code for those two classes can be found below. Once the user picks an image
the image is passed to the DrawingPainter class where paint() is called and I draw my lines and image. The issue is in _paintBackgroundImage() in this method I draw the image by using canvas.drawImage(paintedImage, Offset.zero, Paint()); which does not scale the image.
Earlier I tried a different approach instead of drawing the image with canvas.drawImage(paintedImage, Offset.zero, Paint()) I used canvas.drawImageRect(paintedImage, inputSubRect, outputSubRect, Paint()); as can be seen below. However with this approach the draw picture Is pixelated so I prefer canvas.drawImage(paintedImage, Offset.zero, Paint()) as this does not damage the picture.
Any help with scaling the image will be greatly appreciated.
//Example 1 : Code with canvas.drawImageRect but image pixelated
final UI.Rect rect = UI.Offset.zero & _canvasSize;
final Size imageSize =Size(paintedImage.width.toDouble(), paintedImage.height.toDouble());
FittedSizes sizes = applyBoxFit(BoxFit.contain, imageSize, _canvasSize);
final Rect inputSubRect =
Alignment.center.inscribe(sizes.source, Offset.zero & imageSize);
final Rect outputSubRect =
Alignment.center.inscribe(sizes.destination, rect);
canvas.drawImageRect(paintedImage, inputSubRect, outputSubRect, Paint());
//Example 2 : Code with canvas.drawImageRect but image pixelated
canvas.drawRect(Rect.fromPoints(blurStartOffset, blurIndicatorOffset),
blurPaintSettings)
class DrawingPainter extends CustomPainter {
static int blurColor = 0xFFB3E5FC;
UI.Image paintedImage;
List<DrawingPoints> pointsList;
List<DrawingPoints> blurPointsList;
List<Offset> offsetPoints = List();
Size _canvasSize;
Offset blurIndicatorOffset;
Offset blurStartOffset;
bool isBlur;
List<BlurIndicatorOffsetWrapper> wrapperList = new List();
/// To blur an image we need a [MaskFilter]
Paint blurPaintSettings = new Paint()
..style = PaintingStyle.fill
..color = Color(blurColor)
..maskFilter = MaskFilter.blur(BlurStyle.normal, 3.0);
DrawingPainter(
{this.pointsList,
this.paintedImage,
this.blurPointsList,
this.blurIndicatorOffset,
this.blurStartOffset}) {
isBlur = blurIndicatorOffset != null;
}
#override
void paint(Canvas canvas, Size size) {
_canvasSize = size;
_paintBackgroundImage(canvas);
_drawPoints(canvas);
_drawBlurIndicator(canvas);
}
/// Paints the image onto the canvas
void _paintBackgroundImage(Canvas canvas) {
if (paintedImage == null) {
return;
}
final UI.Rect rect = UI.Offset.zero & _canvasSize;
final Size imageSize =
Size(paintedImage.width.toDouble(), paintedImage.height.toDouble());
FittedSizes sizes = applyBoxFit(BoxFit.contain, imageSize, _canvasSize);
final Rect inputSubRect =
Alignment.center.inscribe(sizes.source, Offset.zero & imageSize);
final Rect outputSubRect =
Alignment.center.inscribe(sizes.destination, rect);
canvas.drawImageRect(paintedImage, inputSubRect, outputSubRect, Paint());
}
/// Paints the lines onto the canvas
void _drawPoints(Canvas canvas) {
for (int i = 0; i < pointsList.length - 1; i++) {
if (pointsList[i] != null && pointsList[i + 1] != null) {
canvas.drawLine(pointsList[i].points, pointsList[i + 1].points,
pointsList[i].paint);
}
}
}
/// Paints the blur indicator onto the canvas
void _drawBlurIndicator(Canvas canvas) {
if (blurStartOffset != null && blurIndicatorOffset != null) {
canvas.drawRect(Rect.fromPoints(blurStartOffset, blurIndicatorOffset),
blurPaintSettings);
}
}
void setBlurIndicator(Offset localOffset) {
blurIndicatorOffset = localOffset;
}
#override
bool shouldRepaint(DrawingPainter oldDelegate) {
return true;
}
Future<Uint8List> save() async {
//Create canvas
// Set PictureRecorder on the canvas and start recording
UI.PictureRecorder recorder = UI.PictureRecorder();
Canvas canvas = Canvas(recorder);
//Draw image on new canvas
if (paintedImage != null) {
final Size imageSize = Size(paintedImage.width.toDouble(), paintedImage.height.toDouble());
//Here image is the problem
canvas.drawImage(paintedImage, Offset.zero, Paint());
}
//Draw points on new canvas
for (int i = 0; i < pointsList.length - 1; i++) {
if (pointsList[i] != null && pointsList[i + 1] != null) {
canvas.drawLine(
pointsList[i].points,
pointsList[i + 1].points,
pointsList[i].paint,
);
}
}
//End recording
final resultImage = await recorder.endRecording().toImage(
_canvasSize.width.floor(),
_canvasSize.height.floor(),
);
final imageBytes =
await resultImage.toByteData(format: UI.ImageByteFormat.png);
return imageBytes.buffer.asUint8List();
}
}
class DrawingPoints {
Paint paint;
Offset points;
DrawingPoints({this.points, this.paint});
}
enum SelectedMode { StrokeWidth, Opacity, Color, Blur }
I had a very similar requirement and the comment about using paintImage was exactly what I was looking for, so I figured I'd share what I ended up with.
I needed to scale down an image and draw overlays on top of that image. image is my original (unscaled) Image object.
var recorder = ui.PictureRecorder();
var imageCanvas = new Canvas(recorder);
var painter = _MarkupPainter(_overlays);
//Paint the image into a rectangle that matches the requested width/height.
//This will handle rescaling the image into the rectangle so that it will not be clipped.
paintImage(
canvas: imageCanvas,
rect: Rect.fromLTWH(0, 0, scaledWidth, scaledHeight),
image: image,
fit: BoxFit.scaleDown,
repeat: ImageRepeat.noRepeat,
scale: 1.0,
alignment: Alignment.center,
flipHorizontally: false,
filterQuality: FilterQuality.high
);
//Add the markup overlays.
painter.paint(imageCanvas, Size(scaledWidth, scaledHeight));
var picture = recorder.endRecording();
return picture.toImage(scaledWidth.toInt(), scaledHeight.toInt());

How to make the clicks from one UIViewController pass into the parent UIViewController through the hole

I have UIViewController (OverlayViewController), which overlay another UIViewController(RootViewController).
In OverlayViewController I add UIView for all frame size:
var overlayView = new UIView(OverlayViewController.View.Frame);
overlayView.Alpha = 0.5f;
overlayView.BackgroundColor = UIColor.Blue;
overlayView.UserInteractionEnabled = false;
View.AddSubview(overlayView);
Then I make a hole:
var path = new CGPath();
var radius = 50.0f;
var xOffset = 100;
var yOffset = 300;
path.AddArc(overlayView.Frame.Width - radius / 2 - xOffset, yOffset, radius, 0.0f, (nfloat) (2 * 3.14), false);
var cgRect = new CGRect(0, 0, overlayView.Frame.Width, overlayView.Frame.Height);
path.AddRect(cgRect);
maskLayer.BackgroundColor = UIColor.Black.CGColor;
maskLayer.Path = path;
maskLayer.FillRule = CAShapeLayer.FillRuleEvenOdd;
overlayView.Layer.Mask = maskLayer;
overlayView.ClipsToBounds = true;
When I set overlayView.UserInteractionEnabled = false; I could touch through OverlayViewController and all clicks worked.
How I can properly use UITapGestureRecognizer so that clicks by items on the RootViewController work only inside the circle? And all the other clicks were blocked.
I tried set overlayView.UserInteractionEnabled = true;, but it doesn't help me:
tap.ShouldReceiveTouch += (recognizer, touch) =>
{
return !path.ContainsPoint(touch.LocationInView(overlayView), true);
};
View.AddGestureRecognizer(tap);
I use Xamarin, but I can understand the decision on Swift too.
I found the solution...
I make custom UIView which can set CGPath.
public class OverlayView : UIView
{
public OverlayView(CGRect frame): base(frame)
{}
private CGPath _path;
public override bool PointInside(CGPoint point, UIEvent uievent)
{
return _path.ContainsPoint(point, true);
}
public void SetPoint(CGPath path)
{
_path = path;
}
}
And set overlayView.SetPoint(path);.
In OverlayView I override PointInside and check my touch points. Also, I set overlayView.UserInteractionEnabled = true;.

Xamarin iOS CustomRenderer button background suddenly not working

I have the following CustomRenderer to apply a style to buttons in my iOS project of my cross-platform Xamarin.Forms app.
The button used to have rounded edges, white text, and a blue gradient background.
Everything has been working just fine up until I downloaded Xcode 8.1. Since moving to 8.1, the same code will not present the background gradient. Can anyone see how I could change my code to get the background gradient working again?
The border radius and text color are all working as usual - it's just the background gradient which is missing.
[assembly: ExportRenderer(typeof(CustomButton), typeof(CustomButtonRenderer))]
namespace MyApp.Forms.iOS.CustomRenderers
{
class CustomButtonRenderer : ButtonRenderer
{
public override void LayoutSubviews()
{
foreach (var layer in Control?.Layer.Sublayers.Where(layer => layer is CAGradientLayer))
{
layer.Frame = Control.Bounds;
}
base.LayoutSubviews();
}
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
var gradient = new CAGradientLayer();
gradient.CornerRadius = Control.Layer.CornerRadius = 10;
gradient.Colors = new CGColor[]
{
UIColor.FromRGB(153, 204, 255).CGColor,
UIColor.FromRGB(51, 102, 204).CGColor
};
var layer = Control?.Layer.Sublayers.LastOrDefault();
Control?.Layer.InsertSublayerBelow(gradient, layer);
Control.SetTitleColor(UIColor.White, UIControlState.Normal);
Control.Layer.BorderColor = UIColor.FromRGB(51, 102, 204).CGColor;
Control.Layer.BorderWidth = 1;
}
}
}
}
Ok so I have finally worked out what was going on. In the LayoutSubviews method where I was setting each layer.Frame to the value of Control.Bounds, I noticed that Control.Bounds was a rectangle full of zeros, so my gradient was therefore 0 pixels in size.
I have modified the method as follows and it now works as expected again:
public override void LayoutSubviews()
{
var newBounds = Element.Bounds.ToRectangleF();
foreach (var layer in Control?.Layer.Sublayers.Where(layer => layer is CAGradientLayer))
{
layer.Frame = new CGRect(0, 0, newBounds.Width, newBounds.Height);
}
base.LayoutSubviews();
}
I'm not sure if this is a hack, but it seems to do the job - for now...

How to fade in and out images in Xamarin iOS

I've created a launch animation in Xamarin for an iOS app but I can't seem to get the images to fade in and out gracefully. I think I need to use the .FadeTo() method but I'm unable to apply it to my code properly. I'm trying to get the first image to fade in then fade out while the second image fades in and so on. Any help is appreciated - I'm a newb!
Here's my code:
using System.Drawing;
using MonoTouch.UIKit;
namespace MapView {
public class ImageViewController : UIViewController {
UIImageView animatedlaunchImage;
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
Title = "Animated ImageView";
View.BackgroundColor = UIColor.White;
// an animating image
animatedlaunchImage = new UIImageView();
animatedlaunchImage.AnimationImages = new UIImage[] {
UIImage.FromBundle ("Splash01.png")
, UIImage.FromBundle ("Splash02.png")
, UIImage.FromBundle ("Splash03.png")
, UIImage.FromBundle ("Splash04.png")
, UIImage.FromBundle ("Splash05.png")
} ;
animatedlaunchImage.AnimationRepeatCount = 1;
animatedlaunchImage.AnimationDuration = 3;
animatedlaunchImage.Frame = new RectangleF(0, 0, 320, 568);
View.AddSubview(animatedlaunchImage);
animatedlaunchImage.StartAnimating ();
}
}
}
Include CoreAnimation and add these lines of code after you create the UIImageView,
CATransition customTransition = new CATransition();
customTransition.FadeInDuration = 1.0f;
customTransition.FadeOutDuration = 1.0f;
customTransition.TimingFunction = CAMediaTimingFunction.FromName(
CAMediaTimingFunction.EaseInEaseOut);
customTransition.Type = CATransition.TransitionFade;
animatedlaunchImage.Layer.AddAnimation(customTransition, null);
Translated from this objective-c question.

Resources