How to draw text on image with semi transparent background in Xamarin iOS? - ios

I have an UIImage I would like to draw text on image. I would like it to look like this:
I am drawing text with this code:
private static UIImage DrawText(UIImage uiImage, string sText, UIColor textColor, int iFontSize)
{
nfloat fWidth = uiImage.Size.Width;
nfloat fHeight = uiImage.Size.Height;
using (CGBitmapContext ctx = new CGBitmapContext(IntPtr.Zero, (nint)fWidth, (nint)fHeight, 8, 4 * (nint)fWidth, CGColorSpace.CreateDeviceRGB(), CGImageAlphaInfo.PremultipliedFirst))
{
ctx.DrawImage(new CGRect(0, 0, (double)fWidth, (double)fHeight), uiImage.CGImage);
ctx.SelectFont("Helvetica", iFontSize, CGTextEncoding.MacRoman);
nfloat fRed, fGreen, fBlue, fAlpha;
textColor.GetRGBA(out fRed, out fGreen, out fBlue, out fAlpha);
ctx.SetFillColor(fRed, fGreen, fBlue, fAlpha);
ctx.SetTextDrawingMode(CGTextDrawingMode.Fill);
ctx.ShowTextAtPoint(fWidth / 4, fHeight / 10, sText);
return UIImage.FromImage(ctx.ToImage());
}
}
It draws text correctly. Problem is it doesn't have grey rectangle around it that fills space with grey. How can I accomplish this?

Related

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;
}

How can I create a gradient effect using color divisions?

I'm trying to create a table view (custom class not UITableView) where its results have a gradient effect like in the example image below:
What I tried:
I successfully managed to add the correct gradient as a background to every table cell but I need it to be the color of the text of each label not the background of each cell. Question. (I asked this one.)
FAILED ATEMPT:
Creating a custom gradient image and adding it as colorWithPatternImage: to each label but since the gradient is one every cell looks the same. Question.
FAILED ATEMPT:
Last thing to try:
Suppose you have two colors, color1 and color2. A gradient can result by mixing these colors. In the picture above color1 = purple and color2 = orange. It would be easy to create a gradient effect by dividing the gradient in sections based on the number of results and then find the average color of each section and use it as the text color of each corresponding result.
For example:
5 results = 5 divisions.
division1 = purple
division2 = less purple, more orange
division3 = equal purple, equal orange
division4 = least purple, most orange
division5 = orange
The result is not as detailed because each text is a solid color but it is equally impressive when the text is small:
The problem is, for two colors like these:
Purple: 128.0, 0.0, 255.0
Orange: 255.0, 128.0, 0.0
How do you divide it in 5 sections and find the average of each section?
I could do this using the eyedropper tool in pixelmator but only if I knew the fixed number of results, won't work with 6 results.
I can't approach it with math, I don't know where to begin.
Any ideas?
You can use math on the rgb values of the colors.
To get the rgb values, you can use the getRed:green:blue:alpha method on UIColor. Then all you have to do is average the colors together based on how many sections you need.
Here is a function that should return an array of colors based on a start and end color, and how many divisions you need.
Solution
func divideColors(firstColor: UIColor, secondColor: UIColor, sections: Int) -> [UIColor] {
// get rgb values from the colors
var firstRed: CGFloat = 0; var firstGreen: CGFloat = 0; var firstBlue: CGFloat = 0; var firstAlpha: CGFloat = 0;
firstColor.getRed(&firstRed, green: &firstGreen, blue: &firstBlue, alpha: &firstAlpha)
var secondRed: CGFloat = 0; var secondGreen: CGFloat = 0; var secondBlue: CGFloat = 0; var secondAlpha: CGFloat = 0;
secondColor.getRed(&secondRed, green: &secondGreen, blue: &secondBlue, alpha: &secondAlpha)
// function to mix the colors
func mix(_ first: CGFloat, _ second: CGFloat, ratio: CGFloat) -> CGFloat { return first + ratio * (second - first) }
// variable setup
var colors = [UIColor]()
let ratioPerSection = 1.0 / CGFloat(sections)
// mix the colors for each section
for section in 0 ..< sections {
let newRed = mix(firstRed, secondRed, ratio: ratioPerSection * CGFloat(section))
let newGreen = mix(firstGreen, secondGreen, ratio: ratioPerSection * CGFloat(section))
let newBlue = mix(firstBlue, secondBlue, ratio: ratioPerSection * CGFloat(section))
let newAlpha = mix(firstAlpha, secondAlpha, ratio: ratioPerSection * CGFloat(section))
let newColor = UIColor(red: newRed, green: newGreen, blue: newBlue, alpha: newAlpha)
colors.append(newColor)
}
return colors
}
Your question is tagged as Objective-C, but it should be easy enough to convert the Swift code above to Objective-C since you would use the same UIColor API.
Here is some code to test the above function (perfect for a Swift playground).
Testing Code
let sections = 5
let view = UIView(frame: CGRect(x: 0, y: 0, width: 250, height: 50))
for (index, color) in divideColors(firstColor: UIColor.purple, secondColor: UIColor.orange, sections: sections).enumerated() {
let v = UIView(frame: CGRect(x: CGFloat(index) * 250 / CGFloat(sections), y: 0, width: 250 / CGFloat(sections), height: 50))
v.backgroundColor = color
view.addSubview(v)
}
view.backgroundColor = .white
Test Result
It also works for any number of sections and different colors!
So you want this:
There are several ways you could implement this. Here's a way that's pixel-perfect (it draws the gradient through the labels instead of making each label a solid color).
Make a subclass of UILabel. In your subclass, override drawTextInRect: to draw the gradient.
Let's declare the subclass like this:
IB_DESIGNABLE
#interface GradientLabel: UILabel
#property (nonatomic, weak) IBOutlet UIView *gradientCoverageView;
#property (nonatomic, strong) IBInspectable UIColor *startColor;
#property (nonatomic, strong) IBInspectable UIColor *endColor;
#end
Connect the gradientCoverageView outlet to a view that covers the entire area of the gradient—so, a view that covers all five of the labels. This could be the superview of the labels, or it could just be a hidden view that you have set up to (invisibly) fill the same area.
Set startColor to purple and endColor to orange.
We'll draw the gradient-filled text (in our drawTextInRect: override) in three steps:
Call super to just draw the text normally.
Set the graphics context blend mode kCGBlendModeSourceIn. This blend mode tells Core Graphics to draw only where the context has already been drawn, and to overwrite whatever was drawn there. Thus Core Graphics treats the text drawn by super as a mask.
Draw the gradient with the start point at the top of the coverage view and the end point at the bottom of the coverage view, not at the top and bottom of the current label. Thus the gradient spans the entire coverage view, but is masked to only show up where the text of the current label was drawn in step 1.
Here's the implementation:
#implementation GradientLabel
- (void)drawTextInRect:(CGRect)rect {
[super drawTextInRect:rect];
CGContextRef gc = UIGraphicsGetCurrentContext();
NSArray *colors = #[(__bridge id)self.startColor.CGColor, (__bridge id)self.endColor.CGColor];
CGGradientRef gradient = CGGradientCreateWithColors(CGBitmapContextGetColorSpace(gc), (__bridge CFArrayRef)colors, NULL);
UIView *coverageView = self.gradientCoverageView ?: self;
CGRect coverageRect = [coverageView convertRect:coverageView.bounds toView:self];
CGPoint startPoint = coverageRect.origin;
CGPoint endPoint = { coverageRect.origin.x, CGRectGetMaxY(coverageRect) };
CGContextSaveGState(gc); {
CGContextSetBlendMode(gc, kCGBlendModeSourceIn);
CGContextDrawLinearGradient(gc, gradient, startPoint, endPoint, 0);
} CGContextRestoreGState(gc);
CGGradientRelease(gradient);
}
#end
Here's the layout in my storyboard:
For all five of the GradientLabel instances, I set the startColor to purple and the endColor to orange and I connected the gradientCoverageView outlet to the enclosing stack view.
Well, here is how to do the solid-color mathy bit at the end:
If you have n + 1 colors, for 0 <= m <= n:
color.red[m] = (purple.red * (m/n)) + (orange.red * ((n-m)/n);
color.green[m] = (purple.green * (m/n)) + (orange.green * ((n-m)/n));
color.blue[m] = (purple.blue * (m/n)) + (orange.blue * ((n-m)/n));
Hope this helps.
For Xamarin iOS C#
public static List<UIColor> DividedColors(UIColor firstColor, UIColor secondColor, int sections)
{
nfloat firstRed = 0;
nfloat firstGreen = 0;
nfloat firstBlue = 0;
nfloat firstAlpha = 0;
firstColor.GetRGBA(out firstRed, out firstGreen, out firstBlue, out firstAlpha);
nfloat secondRed = 0;
nfloat secondGreen = 0;
nfloat secondBlue = 0;
nfloat secondAlpha = 0;
secondColor.GetRGBA(out secondRed, out secondGreen, out secondBlue, out secondAlpha);
nfloat Mix(nfloat first, nfloat second, nfloat ratio)
{
return first + ratio * (second - first);
}
List<UIColor> colors = new List<UIColor>();
var ratioPerSection = 1.0f / sections;
for (int i = 0; i < sections; i++)
{
var newRed = Mix(firstRed, secondRed, ratioPerSection * i);
var newGreen = Mix(firstGreen, secondGreen, ratioPerSection * i);
var newBlue = Mix(firstBlue, secondBlue, ratioPerSection * i);
var newAlpha = Mix(firstAlpha, secondAlpha, ratioPerSection * i);
var newColor = new UIColor(newRed, newGreen, newBlue, newAlpha);
colors.Add(newColor);
}
return colors;
}

how to add some text to a static image programatically, Using MonoTouch?

I have a static image of size 1024*768 with some logo on one side,
i want to have some text added to that image eg: Page 1, (on another side)
i got some code from
public override void ViewDidLoad ()
{
try {
base.ViewDidLoad ();
UIImage ii = new UIImage (Path.Combine (NSBundle.MainBundle.BundleUrl.ToString ().Replace ("%20", " ").Replace ("file://", ""), "images2.png"));
RectangleF wholeImageRect = new RectangleF (0, 0, ii.CGImage.Width, ii.CGImage.Height);
imageView = new UIImageView (wholeImageRect);
this.View.AddSubview (imageView);
imageView.Image = DrawVerticalText ("Trail Text", 100, 100);
Console.Write ("Switch to Simulator now to see ");
Console.WriteLine ("some stupid graphics tricks");
} catch (Exception ex) {
}
}
public static UIImage DrawVerticalText (string text, int width, int height)
{
try {
float centerX = width / 2;
float centerY = height / 2;
//Create the graphics context
byte[] mybyteArray;
CGImage tt = null;
UIImage ii = new UIImage (Path.Combine (NSBundle.MainBundle.BundleUrl.ToString ().Replace ("%20", " ").Replace ("file://", ""), "images2.png"));
using (NSData imagedata = ii.AsPNG ()) {
mybyteArray = new byte[imagedata.Length];
System.Runtime.InteropServices.Marshal.Copy (imagedata.Bytes, mybyteArray, 0, Convert.ToInt32 (imagedata.Length));
using (CGBitmapContext ctx = new CGBitmapContext (mybyteArray, width, height, 8, 4 * width, CGColorSpace.CreateDeviceRGB (), CGImageAlphaInfo.PremultipliedFirst)) {
//Set the font
ctx.SelectFont ("Arial", 16f, CGTextEncoding.MacRoman);
//Measure the text's width - This involves drawing an invisible string to calculate the X position difference
float start, end, textWidth;
//Get the texts current position
start = ctx.TextPosition.X;
//Set the drawing mode to invisible
ctx.SetTextDrawingMode (CGTextDrawingMode.Invisible);
//Draw the text at the current position
ctx.ShowText (text);
//Get the end position
end = ctx.TextPosition.X;
//Subtract start from end to get the text's width
textWidth = end - start;
//Set the fill color to blue
ctx.SetRGBFillColor (0f, 0f, 1f, 1f);
//Set the drawing mode back to something that will actually draw Fill for example
ctx.SetTextDrawingMode (CGTextDrawingMode.Fill);
//Set the text rotation to 90 degrees - Vertical from bottom to top.
ctx.TextMatrix = CGAffineTransform.MakeRotation ((float)(360 * 0.01745329f));
//Draw the text at the center of the image.
ctx.ShowTextAtPoint (2, 2, text);
tt = ctx.ToImage ();
}
}
//Return the image
return UIImage.FromImage (tt);
} catch (Exception ex) {
return new UIImage (Path.Combine (NSBundle.MainBundle.BundleUrl.ToString ().Replace ("%20", " ").Replace ("file://", ""), "images2.png"));
}
}
the output i am getting as following
As you can see it gets completely stretched in terms of width, i need this to be solved Any suggestions ???
At the same time the original image has nothing in the upper part, where as after processing it shows multi coloured layer, how to fix that ??
Why do you not draw your text directly to the image? Perhaps you can try this:
private static UIImage PutTextOnImage(UIImage image, string text, float x, float y)
{
UIGraphics.BeginImageContext(new CGSize(image.Size.Width, image.Size.Height));
using (CGContext context = UIGraphics.GetCurrentContext())
{
// Copy original image
var rect = new CGRect(0, 0, image.Size.Width, image.Size.Height);
context.SetFillColor(UIColor.Black.CGColor);
image.Draw(rect);
// Use ScaleCTM to correct upside-down imaging
context.ScaleCTM(1f, -1f);
// Set the fill color for the text
context.SetTextDrawingMode(CGTextDrawingMode.Fill);
context.SetFillColor(UIColor.FromRGB(255, 0, 0).CGColor);
// Draw the text with textSize
var textSize = 20f;
context.SelectFont("Arial", textSize, CGTextEncoding.MacRoman);
context.ShowTextAtPoint(x, y, text);
}
// Get the resulting image from context
var resultImage = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
return resultImage;
}
The above method draws your text at coords x, y with given color and textsize. If you want it vertically you need to rotate the text with rotateCTM. keep in mind rotateCTM uses radius.
Add this to your using Context block (before DrawTextAtPoint):
var angle = 90;
var radius = 90 * (nfloat)Math.PI / 180;
context.RotateCTM(radius);

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.

Drawing Radial Gradients in Blackberry?

How do I draw a radial gradient button in BlackBerry? I found "Drawing Radial Gradients" on the BlackBerry support forums. All I am able to implement on my own is a linear gradient.
This is a little tricky. Drawing linear gradients on field backgrounds is easy. Drawing radial gradients on field backgrounds is harder. Doing it on a button is harder still.
First of all, the example you link to does indeed look really bad. The biggest problem with that code is that it uses Graphics.drawArc() to construct the gradient out of concentric circles (lines). This is not at all smooth.
The biggest improvement you need to make over that is to use Graphics.fillArc() instead, which will look much smoother (although there may be a performance impact to this ...).
Your question didn't say anything about how you wanted the button to look when focused, or whether the corners needed to be rounded. That's where some of the difficulty comes in.
If you just extend the RIM ButtonField class, you'll probably have trouble with the default drawing for focus, and edge effects. It's probably necessary to directly extend the base Field class in a new, written-from-scratch, button field. I wouldn't necessarily recommend that you do all this yourself, since buttons require focus handling, click handling, etc. You should probably start with something like the BaseButtonField from the BlackBerry AdvancedUI open source library.
I have prototyped this for you, using that class as a base. (so, you'll need to download and include that source file in your project if you use this).
I created a GradientButtonField subclass:
private class GradientButtonField extends BaseButtonField {
private int startR;
private int startG;
private int startB;
private int endR;
private int endG;
private int endB;
/** the maximum distance from the field's center, in pixels */
private double rMax = -1.0;
private int width;
private int height;
private String label;
private int fontColor;
/**
* Create a gradient button field
* #param startColor the integer Color code to use at the button center
* #param endColor the integer Color code to use at the button edges
* #param label the text to show on the button
* #param fontColor color for label text
*/
public GradientButtonField (int startColor, int endColor, String label, int fontColor) {
// record start and end color R/G/B components, to
// make intermediate math easier
startR = (startColor >> 16) & 0xFF;
startG = (startColor >> 8) & 0xFF;
startB = startColor & 0xFF;
endR = (endColor >> 16) & 0xFF;
endG = (endColor >> 8) & 0xFF;
endB = endColor & 0xFF;
this.label = label;
this.fontColor = fontColor;
}
public String getLabel() {
return label;
}
protected void layout(int w, int h) {
width = Math.min(Display.getWidth(), w);
height = Math.min(Display.getHeight(), h);
if (rMax < 0.0) {
rMax = Math.sqrt((width * width)/4.0 + (height * height)/4.0);
}
setExtent(width, height);
}
private int getColor(double scale, boolean highlighted) {
int r = (int)(scale * (endR - startR)) + startR;
int g = (int)(scale * (endG - startG)) + startG;
int b = (int)(scale * (endB - startB)) + startB;
if (highlighted) {
// just brighten the color up a bit
r = (int)Math.min(255, r * 1.5);
g = (int)Math.min(255, g * 1.5);
b = (int)Math.min(255, b * 1.5);
}
return (65536 * r + 256 * g + b);
}
protected void paint(Graphics graphics) {
int oldColor = graphics.getColor();
// we must loop from the outer edge, in, to draw
// concentric circles of decreasing radius, and
// changing color
for (int radius = (int)rMax; radius >= 0; radius--) {
double scale = ((double)radius) / rMax;
boolean focused = (getVisualState() == Field.VISUAL_STATE_FOCUS);
graphics.setColor(getColor(scale, focused));
int x = width / 2 - radius;
int y = height / 2 - radius;
graphics.fillArc(x, y, 2 * radius, 2 * radius, 0, 360);
}
String text = getLabel();
graphics.setColor(fontColor);
graphics.drawText(text,
(width - getFont().getAdvance(text)) / 2,
(height - getFont().getHeight()) / 2);
// reset graphics object
graphics.setColor(oldColor);
}
}
To use this, the Manager that contains the button will need to constrain the button's size in its sublayout() implementation. Or, you can edit my GradientButtonField class to hardcode a certain size (via getPreferredWidth(), layout(), etc.), or whatever you want.
final Field button1 = new GradientButtonField(Color.DARKGRAY, Color.BLUE,
"Click Me!", Color.WHITE);
final Field button2 = new GradientButtonField(Color.DARKGRAY, Color.BLUE,
"Click Me, Too!", Color.WHITE);
Manager mgr = new Manager(Manager.NO_VERTICAL_SCROLL) {
public int getPreferredHeight() {
return Display.getHeight();
}
public int getPreferredWidth() {
return Display.getWidth();
}
protected void sublayout(int maxWidth, int maxHeight) {
setExtent(getPreferredWidth(), getPreferredHeight());
layoutChild(button1, 160, 80);
setPositionChild(button1, 20, 50);
layoutChild(button2, 120, 60);
setPositionChild(button2, 20, 150);
}
};
button1.setChangeListener(new FieldChangeListener() {
public void fieldChanged(Field field, int context) {
Dialog.alert("clicked!");
}
});
mgr.add(button1);
mgr.add(button2);
add(mgr);
I did not round the corners, as that's a bit of work. Depending on what kind of backgrounds you're putting these buttons on, it might be easiest to create a PNG mask image (in your favorite drawing program), which is mostly transparent, and then just has filled corners that mask off the corners of the gradient below it. Then, use Graphics.drawBitmap() in the paint() method above, after you've drawn the radial gradient.
For focus highlighting, I just put in some simple code to brighten the colors when the button is focused. Again, you didn't say what you wanted for that, so I just did something simple.
Here's the result of the code above. The bottom button is focused:

Resources