Thursday, September 16, 2010

Capturing part of an image from a UIImageView

I have a set of views over a picture and I wanted to pick the part of the picture under the views.  I found some code on stack overflow that helped me extract the part of the photo I wanted, however when I used it, it seemed to be scaled improperly and showed up strangely.

I finally figured out that the reason was because the UIImageView scales the image, and I was using the view coordinates of the part I was interested to try and get the subimage from the picture.  This means that when I'm choosing a number from 0-320 in the view and grabbing those coordinates from a 1200x1600 image.  This meant that I was always in the top left part of the picture.

The following code seems pretty close (it might still be a little off) but at least it grabs stuff in the right vicinity.  I  wanted to solve this problem even though I'm currently planning to actually abandon the 'grab' screen and put on separate dialog approach.  Instead I'm going to create an overlay and place it on top of the current view, allowing precise dynamic changes to occur.


- (UIImage*) getView:(CGRect )area
{
    if (self.image==nil)
        return nil;
    if (self.image.image==nil)
        return nil;
    UIGraphicsBeginImageContext(area.size);
    // we are using aspect fit, so we will be using which ever is the larger of these two scale factors.
    float widthScaleFactor=self.image.bounds.size.width/self.image.image.size.width;
    float heightScaleFactor=self.image.bounds.size.height/self.image.image.size.height;
    
    
    float scaleFactor=widthScaleFactor;
    NSLog(@"scale factors are height=%f width=%f",widthScaleFactor,heightScaleFactor);
    if (widthScaleFactor
        scaleFactor=heightScaleFactor;
    
    CGRect drawRect=CGRectMake(-area.origin.x/scaleFactor, -area.origin.y/scaleFactor,
                               self.image.image.size.width,self.image.image.size.height);

    UIImageOrientation currentOrient=self.image.image.imageOrientation;
    
    CGRect destRect=CGRectMake(0, 0, area.size.width, area.size.height);
    CGContextRef context=UIGraphicsGetCurrentContext();
    CGContextClipToRect(context, destRect);
    [self.image.image drawInRect:drawRect];
    UIImage* im=UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return im;
}

Colored RGB sliders, without a XIB

The next step of my refactoring was to modify the Colored View sliders so that they are independent of a .xib file so that I can place them into any view.  This gives me a lot of flexibility and reduces some of the dependance of a xib in a dependent library.  
In addition I found that when I programmatically created the sliders I could change the height and make it look somewhat nicer.


I revised the creation code so that it creates 4 sliders programmatically spaced within a passed in view.  It sets up callbacks in the current routine.

When a color changes it calls a delegate with the changes.


- (void) loadView : (UIView *)viewToUse
{
    NSLog(@"In loadView for ColorEditor");
    // Don't use a nib.
    self.leftMargin=10;
    self.rightMargin=10;
    self.spacing=5;
    self.fontSize=10;
    int labelWidth=74;
    int labelHeight=21;
    
    viewToUse.backgroundColor=[UIColor cyanColor];
    
    if (currentColorSpace==nil)
        currentColorSpace = CGColorSpaceCreateDeviceRGB();
    NSArray *sliderLabels=      [NSArray arrayWithObjects:NSLocalizedString(@"Red", @"Red"), NSLocalizedString(@"Green", @"Green"), NSLocalizedString(@"Blue", @"Blue"),NSLocalizedString(@"Transparency","Transparency"), nil];
    
    
    NSLog(@"view height %f, width %f x %f, y %f",viewToUse.frame.size.height,viewToUse.frame.size.width,viewToUse.frame.origin.x,viewToUse.frame.origin.y);
    
    int sliderHeight=(viewToUse.frame.size.height)/4-spacing;
    
    NSLog(@"SliderHeight %d",sliderHeight);
    
    for (int i=0;i<4;i++)
    {
        CGRect labelFrame=CGRectMake(0, (sliderHeight+spacing)*i+sliderHeight/2-labelHeight/2, labelWidth, labelHeight);
        UILabel *label=[[UILabel alloc] initWithFrame:labelFrame];
        label.adjustsFontSizeToFitWidth=true;
        label.text=[sliderLabels objectAtIndex:i];
        [viewToUse addSubview:label];
        CGRect areaFrame=CGRectMake(leftMargin+labelWidth, (sliderHeight+spacing)*i,viewToUse.bounds.size.width-leftMargin-rightMargin-labelWidth, sliderHeight);
        
        sliders[i]=[[UISlider alloc]init];
        [sliders[i] addTarget:self action:@selector(sliderChanged:) forControlEvents:UIControlEventValueChanged];
        sliders[i].tag=i;        
        sliders[i].accessibilityLabel=[sliderLabels objectAtIndex:i];
        NSLog(@"Y is %d",(sliderHeight+spacing)*i);
        sliders[i].frame=areaFrame;
        [viewToUse addSubview:sliders[i]];
        
     }    
    [self calcColors];
}

and the calc colors is:
- (void) calcColors
{
    for (int index=0;index<4;index++)
    {
        [self setSlider:sliders[index] colorIndex:index];
    }
    
    currentColor=[UIColor colorWithRed:colors[0] green:colors[1] blue:colors[2] alpha:colors[3]];

       
    for (int index=0;index<4;index++)
    {
        // numeric fields are not implemented.      
        //     textFields[index].text=[NSString stringWithFormat:@"%d",(int)  (colors[index]*255)];
    }
    if (delegate!=nil)
    {
        [delegate colorChanged:currentColor];
    }
    
}
This looks like:



Note that the sliders (the area in blue) is the only thing the code does.  The rest is a custom frame done in a .nib.  This enables me to embed the sliders in a view:

The .nib in interface builder looks like this:


Note that I have an image view under a label.  This allows me to change the label and show what it looks like under the label.

The yellow area is my current color for the slider view.  I set it to a unique color so that I can watch what is going on.

I'm allowing the font and background color to be set using the tab bar.  This is not standard behavior for a tab bar, but it is allowable in a modal dialog.   I have a 3rd tab that will eventually be used for a font choice.












The code for this is:
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
    [super viewDidLoad];    
    colorEditor=[[ColorEditor alloc]init];
    isFont=true;
    self.view.backgroundColor=[UIColor colorWithRed:0 green:0 blue:0 alpha:0];
    
}

- (void) initColors:(UIColor*) fColor background:(UIColor*) back text:(NSString*) textValue image:(UIImage*)background {
    [colorEditor loadView:actionView];
    colorEditor.delegate=self;
    label.textColor=fColor;
    label.backgroundColor=back;
    label.text=textValue;
    [colorEditor setCurrentColor:label.textColor];
    backImage.contentMode=UIViewContentModeScaleAspectFit;
    backImage.image=background;

}
- (void) colorChanged:(UIColor*) newColor
{
    if (isFont)
        label.textColor=newColor;
    else
        label.backgroundColor=newColor;
    // Tell our delegate that we have our font colors changed.
    [delegate styleChanged:label.textColor background:label.backgroundColor];

}

- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item // called when a new view is selected by the user (but not programatically)
{
    if (item==fontColor)
    {
        isFont=true;
        [colorEditor setCurrentColor:label.textColor];
         
    }
    else
    {
             isFont=false;
             [colorEditor setCurrentColor:label.backgroundColor];
    }
    if (label !=nil)
    {
        label.text=item.title;   
    }    
    }

@end

I can now change the color editor and use it to set both the background and foreground color of a font.

And the header for this defines another protocol that allows the foreground and background color to be set.

@protocol StyleChanged

- (void) styleChanged:(UIColor*) fontColor background:(UIColor*) backColor;
@end

@interface ColorEdit : UIViewController<UITabBarDelegate,ColorListener> {
    IBOutlet UILabel *label;
    IBOutlet UIView  *actionView;
    IBOutlet UIImageView *backImage;
    ColorEditor *colorEditor;
    IBOutlet UITabBarItem *fontColor;
    IBOutlet UITabBarItem *backColor;
    bool isFont;
    id<StyleChanged> delegate;
    
}
@property (nonatomic,retain) IBOutlet UIImageView *backImage;
@property (nonatomic,retain) IBOutlet UILabel *label;
@property (nonatomic,retain) IBOutlet UIView *actionView;
@property (nonatomic,retain) ColorEditor *colorEditor;
@property (nonatomic,retain) IBOutlet UITabBarItem *fontColor;
@property (nonatomic,retainIBOutlet UITabBarItem *backColor;
@property (nonatomic,retain) id delegate;
- (void) initColors : (UIColor*) fontColor background:(UIColor*) back text:(NSString*) textValue image:(UIImage*)background ;



Friday, September 10, 2010

Programmatically adding views to fit view not working

I was attempting to modify my sliders so that I add the programmatically to fit the available space.  In doing so I was taking an input view and calculating the size that the 4 bars should be to fill up the available space.  I was running into a problem that they weren't displaying correctly.  The bottom bar was always running over.

The weird thing was that all the logging showed that it was working.  I finally figured out that the navigation bar at the top seemed to be messing me up.  I was calculating my view sizes prior to pushing them onto the navigation stack, and that was compressing the data.

The code that calculates and populates my slider where viewToUse is a parameter:
this is done with the loadView command:



    NSLog(@"view height %f, width %f x %f, y %f",viewToUse.frame.size.height,viewToUse.frame.size.width,viewToUse.frame.origin.x,viewToUse.frame.origin.y);
    
    int sliderHeight=(viewToUse.frame.size.height)/4-spacing;
    
    NSLog(@"SliderHeight %d",sliderHeight);
    
    for (int i=0;i<4;i++)
    {
        CGRect labelFrame=CGRectMake(0, (sliderHeight+spacing)*i+sliderHeight/2-labelHeight/2, labelWidth, labelHeight);
        UILabel *label=[[UILabel alloc] initWithFrame:labelFrame];
        label.adjustsFontSizeToFitWidth=true;
        label.text=[sliderLabels objectAtIndex:i];
        [viewToUse addSubview:label];
        CGRect areaFrame=CGRectMake(leftMargin+labelWidth, (sliderHeight+spacing)*i,viewToUse.bounds.size.width-leftMargin-rightMargin-labelWidth, sliderHeight);
        
        sliders[i]=[[UISlider alloc]init];
        [sliders[i] addTarget:self action:@selector(sliderChanged:) forControlEvents:UIControlEventValueChanged];
        sliders[i].tag=i;        
        sliders[i].accessibilityLabel=[sliderLabels objectAtIndex:i];
        NSLog(@"Y is %d",(sliderHeight+spacing)*i);
        sliders[i].frame=areaFrame;
        [viewToUse addSubview:sliders[i]];
        
     }    

This precise code looks very different depending on how my view is initialized:

With a navigation bar:


 NSLog(@"Select color chosen");
    
[self.textView resignFirstResponder];
    ColorEdit *colorEditor=[[ColorEdit alloc] init];
    
    [self.navController pushViewController:colorEditor animated:true];
    [self.navController setNavigationBarHidden:false ];











And with this, (no navigation bar)


self.textView resignFirstResponder];
    ColorEdit *colorEditor=[[ColorEdit allocinit];
    
    [self.navController pushViewController:colorEditor animated:true];
    [self.navController setNavigationBarHidden:true ];















Finally I moved the initialization to another function instead of when the view loads and call this.

  NSLog(@"Select color chosen");
    
[self.textView resignFirstResponder];
    ColorEdit *colorEditor=[[ColorEdit alloc] init];
    
    [self.navController pushViewController:colorEditor animated:true];
    [self.navController setNavigationBarHidden:false ];
    [colorEditor initColors];













Note that this changes the size of the view, probably because it autoresizes then the navigation bar is added.
With the navigation bar it is 241 pixels high.  Without the navigation bar it is 285 pixels high.


The lesson is that when programmatically filling in a view, pay attention to WHEN you do it. If you are going to have other items hanging around check to see if those are changing the heights of your views.  This might throw off your results.  This particular one cost me several hours of debugging, since I was sure my math was correct.

Friday, September 3, 2010

Back from a long absence

I've been taking a break and trying to get back into iphone development.  I decided my wall paper editor needed a navigation controller.  This turned out nastier than I thought.  XCode 4 has some problems with user interface editing, and I also forgot what I was doing.

What I had to do to retrofit a navigation controller onto my existing project.


  1. Add a navigation controller to my main window
  2. Add my main I was using as the view in the navigation controller.  You have to set both the nib and the class to your class.
  3. Add a navigation controller outlet to my application delegate.
  4. Connect my navigation controller to that outlet.
  5. Modify the main of the delegate to do the following:
[window addSubview:rootViewController.view];

where rootViewController is:
@property (nonatomic, retain) IBOutlet UINavigationController *rootViewController;

I spent more time than I care to think doing this, I would miss one step or another and things would just not work.  It was useful practice to wire the thing together, but it ate a lot of time.