zoukankan      html  css  js  c++  java
  • [ios2] UIImageView实现图片的移动和缩放 【转】

    iphone ipad 开发:结合UIImageView实现图片的移动和缩放

    因为种种原因,需要在iphone应用中实现图片查看功能,由于iphone屏幕支持多点触摸,于是是想到用“手势”来实现图片的
    实时缩放和移动。借鉴无所不在的internet网络资料之后,终于实现此一功能,过程如下。

    一、首先实现原图显示(不缩放)
    新建MoveScaleImageView类,继承uiview。用于加载一个UIImage。它有两个主要的成员,
    一个UIImage对象用于指定一个内存图片,一个UIImageView控件用于显示图片。
    @interface MoveScaleImageView : UIView 
    {
    UIImage* originImage;
    UIImageView* imageView;
    }
    -(void)setImage:(UIImage*)_image;
    @end


    @implementation MoveScaleImageView

    -(id)initWithFrame:(CGRect)frame
    {
    if(self=[super initWithFrame:frame]) 
    {
    imageView=[[UIImageView alloc]init];
    [self addSubview:imageView];
    //使图片视图支持交互和多点触摸
    [imageView setUserInteractionEnabled:YES];
    [imageView setMultipleTouchEnabled:YES];
    }
    return self;
    }

    -(void)dealloc
    {
    originImage=nil;
    imageView=nil;
    [super dealloc];
    }

    -(void)setImage:(UIImage *)_image
    {
    originImage=[[UIImage alloc]initWithCGImage:_image.CGImage];
    [imageView setImage:originImage];
    [imageView setFrame:CGRectMake(0, 0, _image.size.width, _image.size.height)];
    //[imageView setNeedsLayout];
    }
    @end

    最主要的就是setImage方法。
    MoveScaleImageView的使用很简单。在ViewController中构造一个MoveScaleImageView,
    然后用一个加载了图片文件的UIImage对象设置其image成员:

    UIImage* image=[UIImage imageNamed:@"df.jpg"];
    MoveScaleImageView*fileContent = [[MoveScaleImageView alloc]initWithFrame:CGRectMake(0, 44, 320, 436)];
    [fileContent setImage:image];

    由于在这里我们没有对图片进行任何的缩放处理,对于小图片会位于屏幕的左上角,并在其他地方留下空白;
    对于尺寸大于屏幕的图片,则图片不能完全显示:


    一、识别手势(单点触摸与多点触摸)
    要想识别手势(gesture),必须响应4个手势的通知方法(参考“iphone3开发基础教程”第13章的内容):
    touchesBegan,touchesMoved,touchesEnded和touchesCancelled。
    首先,我们先来考虑单点触摸情况,这比较简单一些。在单点触摸情况下,移动手指,imageView中的图片可以被拖动,
    这样,对于比较大的图片,我们可以通过拖动来浏览图片的各个部分,当然,对于能一次显示下全部的图片就不需要拖动了。

    修改类MoveScaleImageView,在.h中增加一些声明:
    @interface MoveScaleImageView : UIView 
    {
    UIImage* originImage;
    UIImageView* imageView;
    CGPoint gestureStartPoint;//手势开始时起点
    CGFloat offsetX,offsetY;//移动时x,y方向上的偏移量
    CGFloat curr_X,curr_Y;//现在截取的图片内容的原点坐标
    }
    -(void)setImage:(UIImage*)_image;
    -(void)moveToX:(CGFloat)x ToY:(CGFloat)y;
    @end

    然后实现touchesBegan和touchesMoved方法。
    touchesBegan方法比较简单,记录下手指第一次触摸的位置。因为任何一个拖动都必然有一个起点和终点。
    -(void)touchesBegan:(NSSet *)touches 
      withEvent:(UIEvent *)event
    {
    UITouch *touch=[touches anyObject];
    gestureStartPoint=[touch locationInView:self];
    //NSLog(@”touch:%f,%f”,gestureStartPoint.x,gestureStartPoint.y);
    }

    然后是手指移动后回调的touchesMoved方法:
    -(void)touchesMoved:(NSSet *)touches 
              withEvent:(UIEvent *)event
    {
    UITouch* touch=[touches anyObject];
    CGPoint curr_point=[touch locationInView:self];
    //分别计算x,和y方向上的移动
    offsetX=curr_point.x-gestureStartPoint.x;
    offsetY=curr_point.y-gestureStartPoint.y;
    //只要在任一方向上移动的距离超过Min_offset,判定手势有效
    if(fabsf(offsetX)>= min_offset||fabsf(offsetY)>=min_offset)
    {
    [self moveToX:offsetX ToY:offsetY];
    gestureStartPoint.x=curr_point.x;
    gestureStartPoint.y=curr_point.y;
    }
    }

    在这里我们做了一个简单的判断,只有手指移动了超过一定像素(min_offset常量)后,才识别为拖动手势,
    并调用moveToX方法。在这个方法中,需要不断的更新手指移动的坐标,因为这是一个连续的过程。
    -(void)moveToX:(CGFloat)x ToY:(CGFloat)y
    {
    //计算移动后的矩形框,原点x,y坐标,矩形宽高
    CGFloat destX,destY,destW,destH;
    curr_X=destX=curr_X-x;
    curr_Y=destY=curr_Y-y;
    destW=self.frame.size.width;
    destH=self.frame.size.height;
    if(destX<0) 
    {//左边界越界处理
    curr_X=destX=0;
    }
    if(destY<0) 
    {//上边界越界处理
    curr_Y=destY=0;
    }
    if(destX+destW>originImage.size.width) 
    {//右边界越界处理
    curr_X=destX=originImage.size.width-destW;
    }
    if(destY+destH>originImage.size.height) 
    {//右边界越界处理
    curr_Y=destY=originImage.size.height-destH;
    }
    //创建矩形框为本fame
    CGRect rect = CGRectMake(destX, destY, self.frame.size.width, self.frame.size.height);
    imageView.image=[UIImage imageWithCGImage:CGImageCreateWithImageInRect([originImage CGImage], rect)];
    }
    在这个方法中,我们采用了一种特殊的处理方式:截取大图片的一部分,并将截取部分显示在imageView里。
    我这样做的理由,是因为这是最简单、最容易的实现方式。我参考过网上的几种实现方式,
    发现基本上都需要使用Quartz2D API,并且实现起来要复杂得多。最终从闭路电视监控系统中
    得到了启发(想象一下,安保人员通过移动鼠标控制镜头移动的场景)。

    我们设计了一个矩形框,用它作为模拟的镜头:
    CGRect lensRect; //设置镜头的大小
    同时还设计了一个全局变量用于记录图片缩放过程中的缩放倍率:
    CGFloat scale;   //缩放比例
    当跟踪到手指移动时,让“镜头”做反向运动(为什么是反向运动?因为我们模拟的是“拖动”效果,而不是“跟踪”效果,
    二者是恰恰相反的)。并通过 UIImage imageWithCGImage:CGImageCreateWithImageInRect方法,
    将镜头中的图像捕捉到imageView中。
    这样,移动操作实际上转换成了计算矩形框的位置。当然,我们也要做好边界判断,否则当矩形框超出图片原来的范围时,
    会发生扭曲缩放的现象。


    接下来看怎样识别多点触摸。识别单点触摸和多点触摸其实非常简单,判断touchesBegan的touches参数的count属性即可:
    -(void)touchesBegan:(NSSet *)touches 
              withEvent:(UIEvent *)event
    {
    if([touches count]==2) 
    {//识别两点触摸,并记录两点间距离
    NSArray* twoTouches=[touches allObjects];
    originSpace=[self spaceToPoint:[[twoTouches objectAtIndex:0] locationInView:self]
                         FromPoint:[[twoTouches objectAtIndex:1] locationInView:self]];
    }
    else if([touches count]==1)
    {
    UITouch *touch=[touches anyObject];
    gestureStartPoint=[touch locationInView:self];
    }
    }
    在上面的方法中,我们根据touches的count判断是否是单点触摸并进行分别的处理。
    对于2点触摸,我们记录了两指间的距离并记录在全局的CGFloat变量originSpace中。
    spaceToPoint方法是一个简单函数,使用中学中学过的3角函数计算2点间距离:
    -(CGFloat)spaceToPoint:(CGPoint)first 
     FromPoint:(CGPoint)two
    {//计算两点之间的距离
    float x = first.x – two.x;
    float y = first.y – two.y;
    return sqrt(x * x + y * y);
    }

    在两点触摸中,需要识别2个手势:外向捏合、内向捏合。通常前者使图像放大,而后者可使图像缩小。
    在touchesMoved方法中,这样处理:
    -(void)touchesMoved:(NSSet *)touches 
              withEvent:(UIEvent *)event
    {
    if([touches count]==2) 
    {
    NSArray* twoTouches=[touches allObjects];
    CGFloat currSpace=[self spaceToPoint:[[twoTouches objectAtIndex:0] locationInView:self]
                               FromPoint:[[twoTouches objectAtIndex:1] locationInView:self]];
     
    //如果先触摸一根手指,再触摸另一根手指,则触发touchesMoved方法而不是touchesBegan方法
    //此时originSpace应该是0,我们要正确设置它的值为当前检测到的距离,否则可能导致0除错误
    if(originSpace==0) 
    {
    originSpace=currSpace;
    }
    if(fabsf(currSpace-originSpace)>=min_offset) 
    {//两指间移动距离超过min_offset,识别为手势“捏合”
    CGFloat s=currSpace/originSpace; //计算缩放比例
    [self scaleTo:s];
    }
    }
    else if([touches count]==1)
    {
    …… (省略了部分代码)
    }
    }

    先简单判断了是否为有效捏合(我们为此定义了一个常量min_offset),如果是,则计算手指有效移动长度和
    手势开始时的两指间距的商,以此作为缩放比例。然后调用scaleTo方法:
    -(void)scaleTo:(CGFloat)x
    {
    scale*=x;
    //缩放限制:>=0.1,<=10
    scale=(scale<0.1)?0.1:scale;
    scale=(scale>10)?10:scale;
    //重设imageView的frame
    [self moveToX:0 ToY:0];
    }

    这里,为防止用户无限制的对图像进行“捏合”操作,我们限制了scale的值在0.1-10之间
    (当然你可以将这个阀值定义为常量)。然后调用了一个原地的移动操作,即前面的moveTo方法。
    然而为支持缩放下的图片移动,这个方法被我们更改了:
    -(void)moveToX:(CGFloat)x 
               ToY:(CGFloat)y
    {
    CGPoint point=CGPointMake(x, y);
    //重设镜头
    [self resetLens:point];
    imageView.image=[UIImage imageWithCGImage:
           CGImageCreateWithImageInRect([originImage CGImage], lensRect)];
    [imageView setFrame:
           CGRectMake(0, 0, lensRect.size.width*scale, vlensRect.size.height*scale)];
    }

    其中更多的代码被我们移到了另一个方法resetLens中:
    -(void)resetLens:(CGPoint)point
    {//设置镜头大小和位置
    CGFloat x,y,width,height;
    //===========镜头初始大小=========
    width=self.frame.size.width/scale;
    height=self.frame.size.height/scale;
    //===========调整镜大小不得超过图像实际大小==========
    if(width>originImage.size.width)
    {
    width=originImage.size.width;
    }
    if(height>originImage.size.height) 
    {
    height=originImage.size.height;
    }
    //计算镜头移动的位置(等比缩放)
    x=lensRect.origin.x-point.x/scale;
    y=lensRect.origin.y-point.y/scale;
    //左边界越界处理
    x=(x<0)?0:x;
    //上边界越界处理
    y=(y<0)?0:y;
    //右边界越界
    x=(x+width>originImage.size.width)?originImage.size.width-x;
    //下边界越界处理
    y=(y+height>originImage.size.height)?originImage.size.height-height:y;
    //镜头等比缩放
    lensRect=CGRectMake(x, y, width, height);
    }

    这些代码跟原来moveToX方法中的代码有些许的不同,主要是增加了对scale变量的引入,因为在缩放模式下,
    镜头的移动都是被scale系数缩放过的。通代码中的注释,我们不难理解整个代码。

    这样,大图片经过“捏合”操作可以在屏幕上完全显示出来(上面原来基本看不清楚的第2张图片现在是一台苹果电脑):
    当然,把小图片“捏合”放大成大图片也是可以的。此外通过手指的移动,能查看图片的不同部分
     
     
     

    图片的处理大概就分 截图(capture), 缩放(scale),设定大小(resize), 存储(save)
    这几样比较好处理, 另外还有滤镜,擦试等, 以后再说
    在这个Demo code裡, 我写了几个方法


    1.等比率缩放
    - (UIImage *)scaleImage:(UIImage *)image toScale:(float)scaleSize

    {

    UIGraphicsBeginImageContext(CGSizeMake(image.size.width * scaleSize, image.size.height * scaleSize);
    [image drawInRect:CGRectMake(0, 0, image.size.width * scaleSize, image.size.height * scaleSize)];
    UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();


    return scaledImage;

    }


    2.自定长宽
    - (UIImage *)reSizeImage:(UIImage *)image toSize:(CGSize)reSize

    {
    UIGraphicsBeginImageContext(CGSizeMake(reSize.width, reSize.height));
    [image drawInRect:CGRectMake(0, 0, reSize.width, reSize.height)];
    UIImage *reSizeImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();


    return reSizeImage;

    }


    3.处理某个特定View
    只要是继承UIView的object 都可以处理
    必须先import QuzrtzCore.framework


    -(UIImage*)captureView:(UIView *)theView

    {
    CGRect rect = theView.frame;
    UIGraphicsBeginImageContext(rect.size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    [theView.layer renderInContext:context];
    UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();


    return img;

    }


    4.储存图片
    储存图片这里分成储存到app的文件里, 储存到手机的图片库里

    1) 储存到app的文件里
    NSString *path = [[NSHomeDirectory()stringByAppendingPathComponent:@"Documents"]stringByAppendingPathComponent:@"image.png"];
    [UIImagePNGRepresentation(image) writeToFile:pathatomically:YES];
    這樣就把你要處理的圖片, 以image.png這個檔名存到app home底下的Documents目錄裡

    2)储存到手机的图片库里
    CGImageRef screen = UIGetScreenImage();
    UIImage* image = [UIImage imageWithCGImage:screen];
    CGImageRelease(screen);
    UIImageWriteToSavedPhotosAlbum(image, self, nil, nil);
    UIGetScreenImage()原本是private(私有)api, 用來截取整個畫麵
    不過SDK 4.0後apple就開放了

    另外儲存到手機的圖片庫裡, 必須在實機使用, 模擬器無法使用


    以下代碼用到了Quartz Framework和Core Graphics Framework. 在workspace的framework目錄裏添加這兩個framework.在UIKit裏,圖像類UIImage和CGImageRef的畫圖操作 都是通過Graphics Context來完成。Graphics Context封裝了變換的參數,使得在不同的坐標係裏操作圖像非常方便。缺點就是,獲取圖像的數據不是那麼方便。下麵會給出獲取數據區的代碼。



    從UIView中獲取圖像相當於窗口截屏。ios提供全局的全屏截屏函數UIGetScreenView(). 如果需要特定區域的圖像,可以crop一下。

    1. CGImageRef screen = UIGetScreenImage();
    2. UIImage* image = [UIImage imageWithCGImage:screen];

    對於特定UIView的截屏,可以把當前View的layer,輸出到一個ImageContext中,然後利用這個ImageContext得到UIImage

    1. -(UIImage*)captureView: (UIView *)theView
    2. {
    3. CGRect rect = theView.frame;
    4. UIGraphicsBeginImageContext(rect.size);
    5. CGContextRef context =UIGraphicsGetCurrentContext();
    6. [theView.layer renderInContext:context];
    7. UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
    8. UIGraphicsEndImageContext();

    9. return img;
    10. }

    如果需要裁剪製定區域,可以path & clip,以下例子是建一個200x200的圖像上下文,再截取出左上角

    1. UIGraphicsBeginImageContext(CGMakeSize(200,200));
    2. CGContextRefcontext=UIGraphicsGetCurrentContext();
    3. UIGraphicsPushContext(context);
    4. // ...把图写到context中,省略[indent]CGContextBeginPath();
    5. CGContextAddRect(CGMakeRect(0,0,100,100));
    6. CGContextClosePath();[/indent]CGContextDrawPath();
    7. CGContextFlush(); // 强制执行上面定义的操作
    8. UIImage* image = UIGraphicGetImageFromCurrentImageContext();
    9. UIGraphicsPopContext();

    存储图像分为存储到home目录文件和图片库文件。存储到目录文件是这样

    1. NSString *path = [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"] stringByAppendingPathComponent:@"image.png"];
    2. [UIImagePNGRepresentation(image) writeToFile:path atomically:YES];

    若要存储到图片库里面

    1. UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);


    UImage封装了CGImage, 互相转换很容易

    1. UIImage* imUI=nil;
    2. CGImageRef imCG=nil;
    3. imUI = [UIImage initWithCGImage:imCG];
    4. imCG = imUI.CGImage;

    從CGImage上獲取圖像數據區,在apple dev上有QA, 不過好像還不支持ios
    下麵給出一個在ios上反色的例子

    1. -(id)invertContrast:(UIImage*)img
    2. {
    3. CGImageRef inImage = img.CGImage; 
    4. CGContextRef ctx;
    5. CFDataRef m_DataRef;
    6. m_DataRef = CGDataProviderCopyData(CGImageGetDataProvider(inImage)); 

    7. int width = CGImageGetWidth( inImage );
    8. int height = CGImageGetHeight( inImage );

    9. int bpc = CGImageGetBitsPerComponent(inImage);
    10. int bpp = CGImageGetBitsPerPixel(inImage);
    11. int bpl = CGImageGetBytesPerRow(inImage);

    12. UInt8 * m_PixelBuf = (UInt8 *) CFDataGetBytePtr(m_DataRef);
    13. int length = CFDataGetLength(m_DataRef);

    14. NSLog(@"len %d", length);
    15. NSLog(@"width=%d, height=%d", width, height);
    16. NSLog(@"1=%d, 2=%d, 3=%d", bpc, bpp,bpl);

    17. for (int index = 0; index < length; index += 4)
    18. m_PixelBuf[index + 0] = 255 - m_PixelBuf[index + 0];// b
    19. m_PixelBuf[index + 1] = 255 - m_PixelBuf[index + 1];// g
    20. m_PixelBuf[index + 2] = 255 - m_PixelBuf[index + 2];// r
    21. }

    22. ctx = CGBitmapContextCreate(m_PixelBuf, width, height, bpb, bpl, CGImageGetColorSpace( inImage ), kCGImageAlphaPremultipliedFirst );
    23. CGImageRef imageRef = CGBitmapContextCreateImage (ctx);
    24. UIImage* rawImage = [UIImage imageWithCGImage:imageRef];
    25. CGContextRelease(ctx);
    26. return rawImage;
    27. }

    得到圖像數據區後就可以很方便的實現圖像處理的算法。下麵給顯示圖像數據區的方法,也就是unsigned char*轉為graphics context或者UIImage或和CGImageRef

    1. CGContextRef ctx = CGBitmapContextCreate(pixelBuf,width,height, bitsPerComponent,bypesPerLine, colorSpace,kCGImageAlphaPremultipliedLast );
    2. CGImageRef imageRef = CGBitmapContextCreateImage (ctx);
    3. UIImage* image = [UIImage imageWithCGImage:imageRef];
    4. NSString* path = [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"] stringByAppendingPathComponent:@"ss.png"];
    5. [UIImagePNGRepresentation(self.image) writeToFile:path atomically:YES];
    6. CGContextRelease(ctx);

    關於圖像獲取方麵,在這裏應該都覆蓋到了,不正之處,歡迎指正。

  • 相关阅读:
    C#文件拖放至窗口的ListView控件获取文件类型
    android内存释放处理
    赵雅智_运用Bitmap和Canvas实现图片显示,缩小,旋转,水印
    POJ 3070 Fibonacci 矩阵高速求法
    poj 3261 后缀数组 找反复出现k次的子串(子串能够重叠)
    Codeforces Round #313 C. Gerald&#39;s Hexagon(放三角形)
    HTTP服务端JSON服务端
    iPad popView封装
    OpenCv 人脸检測的学习
    《深入理解java虚拟机》:类的初始化
  • 原文地址:https://www.cnblogs.com/jinjiantong/p/3134934.html
Copyright © 2011-2022 走看看