iOS_24_画画板(含取色板)
来源:互联网 发布:网络拓扑图服务器图标 编辑:程序博客网 时间:2024/04/27 13:31
最终效果如下:
一、简单说明 1、使用一个数组 strokesArr(笔画数组)记录所有笔画,数组中保存的是一个个的笔画字典,一个字典就是一个笔画,笔画字典中有三项:笔画的大小、颜色、pointsArrInOneStroke数组,(保存的是touch begin时的落笔点和touch move过程中经过的点) 2、绘制的时候,从strokesArr(笔画数组)里取出每一个字典(一个字典就是一个笔画),根据字典中笔画的大小、颜色、笔画所经过的点坐标(pointsArrInOneStroke数组),使用UIBezierPath类完成笔画绘制 二、撤销和回撤 一个笔画就是一个字典。 撤销: 使用abandonedStrokesArr (被丢弃的笔画数组)保存要撤销的笔画,即所有笔画数组中的最后一划, 同时将 strokesArr 笔画数组中的最后一个元素删除。 反之,重做: 即将abandonedStrokesArr (被丢弃的笔画数组)中最后一个元素添加到所有笔画数组中,同时将(被丢弃的笔画数组)中的最后一个元素删除。
Main.storyboard
主控制器
Canvas类封装了画画的所有核心代码
方法列表
//// Canvas.h// 24_Canvas画画板//// Created by beyond on 14-8-26.// Copyright (c) 2014年 com.beyond. All rights reserved./* 一、简单说明 1、使用一个数组 strokesArr(笔画数组)记录所有笔画,数组中保存的是一个个的笔画字典,一个字典就是一个笔画,笔画字典中有三项:笔画的大小、颜色、pointsArrInOneStroke数组,(保存的是touch begin时的落笔点和touch move过程中经过的点) 2、绘制的时候,从strokesArr(笔画数组)里取出每一个字典(一个字典就是一个笔画),根据字典中笔画的大小、颜色、笔画所经过的点坐标(pointsArrInOneStroke数组),使用UIBezierPath类完成笔画绘制 二、撤销和回撤 一个笔画就是一个字典。 撤销: 使用abandonedStrokesArr (被丢弃的笔画数组)保存要撤销的笔画,即所有笔画数组中的最后一划, 同时将 strokesArr 笔画数组中的最后一个元素删除。 反之,重做: 即将abandonedStrokesArr (被丢弃的笔画数组)中最后一个元素添加到所有笔画数组中,同时将(被丢弃的笔画数组)中的最后一个元素删除。 */#import <UIKit/UIKit.h>// 自定义的颜色选择控制器,点击之后,它会告诉代理,选中了什么颜色@class ColorPickerController;@interface Canvas : UIView#pragma mark - 属性列表 // 标签,显示笔刷大小@property (nonatomic,retain) IBOutlet UILabel *labelSize;// 滑块 笔刷大小@property (nonatomic,retain) IBOutlet UISlider *sliderSize;// 三个按钮,分别是撤销、重做、清除@property (nonatomic,retain) IBOutlet UIBarButtonItem *undoBtn;@property (nonatomic,retain) IBOutlet UIBarButtonItem *redoBtn;@property (nonatomic,retain) IBOutlet UIBarButtonItem *clearBtn;// toolBar,目的是截图的时候,隐藏掉toolBar@property (nonatomic,retain) IBOutlet UIToolbar *toolBar;#pragma mark - 方法列表// 初始化所有的准备工作-(void) viewJustLoaded;// 选择相册 被点击-(IBAction) didClickChoosePhoto;// 滑块滑动,设置笔刷大小-(IBAction) setBrushSize:(UISlider*)sender;// 撤销 被点击-(IBAction) undo;// 重做 被点击-(IBAction) redo;// 清除画布 被点击-(IBAction) clearCanvas;// 保存图片 被点击-(IBAction) savePic;// 颜色选择 被点击- (IBAction) didClickColorButton;// 重要~~开放给另一个控制器调用,它在调用代理时,会传入参数:即选择好的颜色- (void) pickedColor:(UIColor*)color;@end
核心代码
//// Canvas.h// 24_Canvas画画板//// Created by beyond on 14-8-26.// Copyright (c) 2014年 com.beyond. All rights reserved./* 这儿仅仅是做演示demo,直接让Canvas与控制器绑定,开始画画,监听事件 如果,要更好的抽取出来,则需要创建一个模型类(model)来提供数据源(比如_strokesArr,_abandonedStrokesArr),供CanvasView显示 UIView的setNeedsDisplay和setNeedsLayout方法 首先两个方法都是异步执行的。而setNeedsDisplay会调用自动调用drawRect方法,这样可以拿到 UIGraphicsGetCurrentContext,就可以画画了。 UIUserInterfaceIdiomPad iPad上专用 */#import "Canvas.h"#import "ColorPickerController.h"#import "BeyondViewController.h"@interface Canvas ()<UIImagePickerControllerDelegate,UINavigationControllerDelegate>{ // 所有笔画NSMutableArray *_strokesArr; // 丢弃(撤销)的笔画NSMutableArray *_abandonedStrokesArr; // 当前笔刷颜色UIColor *_currentColor; // 当前的笔刷大小float currentSize; // 选中的图片UIImage *_pickedImg; // 截屏图片UIImage *_screenImg; // 自定义的 颜色选择控制器ColorPickerController *_colorPickerCtrl; // 相片选择器UIImagePickerController *_imagePickerCtrl;}@end@implementation Canvas#pragma mark - 生命周期方法// 禁止多点触摸-(BOOL)isMultipleTouchEnabled {return NO;}// 最重要的画图方法- (void) drawRect: (CGRect) rect{ // 1.先把获取的图片,画到画布上 [self drawPickedImgToCanvas]; // 2.如果【笔画数组】有笔画字典,则按顺序将笔画取出,画到画布上 [self drawStrokesArrToCanvas]; }// 1.先把获取的图片,画到画布上- (void)drawPickedImgToCanvas{int width = _pickedImg.size.width;int height = _pickedImg.size.height;CGRect rectForImage = CGRectMake(0, 0, width, height);[_pickedImg drawInRect:rectForImage];}// 2.如果【笔画数组】有笔画字典,则按顺序将笔画取出,画到画布上- (void)drawStrokesArrToCanvas{ // 如果【笔画数组】为空,则直接返回if (_strokesArr.count == 0) return; // 遍历【笔画数组】,取出每一个笔画字典,每一次迭代,画一个stroke for (NSDictionary *oneStrokeDict in _strokesArr) { // 取出点数组 NSArray *pointsArr = [oneStrokeDict objectForKey:@"points"]; // 取出颜色 UIColor *color = [oneStrokeDict objectForKey:@"color"]; // 取出笔刷尺寸 float size = [[oneStrokeDict objectForKey:@"size"] floatValue]; // 设置颜色 [color set]; // line segments within a single stroke (path) has the same color and line width // 画一个stroke, 一条接着一条,使用圆接头 round joint // 创建一个贝塞尔路径 UIBezierPath* bezierPath = [UIBezierPath bezierPath]; // 点数组 中的第一个,就是 起点 CGPoint startPoint = CGPointFromString([pointsArr objectAtIndex:0]); // 将路径移动到 起点 [bezierPath moveToPoint:startPoint]; // 遍历点数组,将每一个点,依次添加到 bezierPath for (int i = 0; i < (pointsArr.count - 1); i++) { // 依次取出下一个点 CGPoint pointNext = CGPointFromString([pointsArr objectAtIndex:i+1]); // 添加到路径 [bezierPath addLineToPoint:pointNext]; } // 设置线宽 bezierPath.lineWidth = size; // 线连接处为 圆结头 bezierPath.lineJoinStyle = kCGLineJoinRound; // 线两端为 圆角 bezierPath.lineCapStyle = kCGLineCapRound; // 调用路径的方法 画出一条线 [bezierPath stroke]; }}// 重要~~~初始化所有东东-(void) viewJustLoaded { // 1.初始化颜色选择控制器 [self addColorPickerCtrl];// 2.初始化【相片选择器】 [self addUIImagePickerCtrl]; // 3.其他成员初始化 // 【笔画数组】_strokesArr = [NSMutableArray array]; // 【被丢弃的笔画数组】_abandonedStrokesArr = [NSMutableArray array]; // 笔画大小currentSize = 5.0; // toolBar上笔画标签显示文字self.labelSize.text = @"Size: 5"; // 设置笔刷 黑色[self setStrokeColor:[UIColor blackColor]]; // 4.设置重做、撤销、清空三个按钮的状态 [self updateToolBarBtnStatus];}// 1.初始化颜色选择控制器- (void)addColorPickerCtrl{ // 1.添加【颜色选择控制器】ColorPickerController,因为要添加到主控制器中 BeyondViewController *mainVC = [BeyondViewController sharedBeyondViewController];// 初始化自己封装的颜色选择控制器,并设置好代理,目的是颜色设置好了之后,回调告诉当前的canvas画布_colorPickerCtrl = [[ColorPickerController alloc] init];_colorPickerCtrl.pickedColorDelegate = self; // 控制器成为父子关系,视图也成为父子关系 [mainVC addChildViewController:_colorPickerCtrl]; [mainVC.view addSubview:_colorPickerCtrl.view]; // 暂时隐藏【颜色选择控制器】,只有在点击了ToolBar上面的按钮时候,才显示出来 _colorPickerCtrl.view.hidden = YES;}// 2.初始化【相片选择器】- (void)addUIImagePickerCtrl{if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) {_imagePickerCtrl = [[UIImagePickerController alloc] init];_imagePickerCtrl.delegate = self;_imagePickerCtrl.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; // 2) 设置允许修改 // [_imagePickerCtrl setAllowsEditing:YES];}}// 3.自定义方法,设置 撤销、重做、清空三个按钮的可点击状态- (void)updateToolBarBtnStatus{ _redoBtn.enabled = _abandonedStrokesArr.count > 0; _undoBtn.enabled = _strokesArr.count > 0; _clearBtn.enabled = _strokesArr.count > 0; }#pragma mark - 控件连线方法// 滑块滑动- (IBAction)setBrushSize:(UISlider*)sender{currentSize = sender.value;self.labelSize.text = [NSString stringWithFormat:@"Size: %.0f",sender.value];}// 撤销按钮点击事件-(IBAction) undo { // 如果笔画数组中有笔画字典if ([_strokesArr count]>0) { // 最后一个笔画字典,即,被丢弃的笔画字典NSMutableDictionary* abandonedStrokeDict = [_strokesArr lastObject]; // 将最后一个笔画字典,添加到被丢弃的笔画字典数组里面保存,以供drawRect[_abandonedStrokesArr addObject:abandonedStrokeDict]; // 从所有笔画数组中移除掉最后一笔[_strokesArr removeLastObject]; // 重新调用drawRect进行绘制[self setNeedsDisplay];} // 2.设置重做、撤销、清空三个按钮的状态 [self updateToolBarBtnStatus];}// 重做-(IBAction) redo { // 如果 被丢弃的笔画数组,里面有值if ([_abandonedStrokesArr count]>0) { // 取出最后一个被仍进来的 笔画字典,(即最先书写的,而且是在撤销的操作里面,最后被添加到【被丢弃的笔画数组】)NSMutableDictionary* redoStrokeDict = [_abandonedStrokesArr lastObject]; // 将需要重画的笔画字典,添加到【所有笔画数组】中[_strokesArr addObject:redoStrokeDict]; // 并且,从【被丢弃的笔画数组】中移除,该笔画字典[_abandonedStrokesArr removeLastObject]; // 重新调用drawRect进行绘制[self setNeedsDisplay];} // 2.设置重做、撤销、清空三个按钮的状态 [self updateToolBarBtnStatus];}// 清空画布,只需清空【所有笔画数组】和【被丢弃的笔画数组】-(IBAction) clearCanvas { // 建议不要将选择出来的背景图片清空,只清空没写好的笔画算了// _pickedImg = nil;[_strokesArr removeAllObjects];[_abandonedStrokesArr removeAllObjects]; // 重新调用drawRect进行绘制[self setNeedsDisplay]; // 2.设置重做、撤销、清空三个按钮的状态 [self updateToolBarBtnStatus];}// 保存图片-(IBAction) savePic {// 暂时移除 工具条//[_toolBar removeFromSuperview]; // 截图代码 // 1,开启上下文UIGraphicsBeginImageContext(self.bounds.size); // 2.将图层渲染到上下文[self.layer renderInContext:UIGraphicsGetCurrentContext()]; // 开启上下文,使用参数之后,截出来的是原图(YES 0.0 质量高) //UIGraphicsBeginImageContextWithOptions(self.frame.size, YES, 0.0); // 3.从上下文中取出图片_screenImg = UIGraphicsGetImageFromCurrentImageContext(); // 4.关闭上下文UIGraphicsEndImageContext();// 重新添加 工具条,并置最上方//[self addSubview:_toolBar];//[self bringSubviewToFront:self.labelSize];// 调用自定义方法,保存截屏到相册[self performSelector:@selector(saveToPhoto) withObject:nil afterDelay:0.0];}// 自定义方法,保存截屏到相册-(void) saveToPhoto {// 一句话,写到相册UIImageWriteToSavedPhotosAlbum(_screenImg, nil, nil, nil); // UIAlertView 提示成功UIAlertView* alertView= [[UIAlertView alloc] initWithTitle:nil message:@"Image Saved" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];[alertView show]; }// 点击选择颜色按钮- (IBAction) didClickColorButton { // 显示或隐藏 自己的【颜色选择控制器】 _colorPickerCtrl.view.hidden = !_colorPickerCtrl.view.hidden; }// 当_colorPickerCtrl选择颜色完毕,会调用代理 的本方法- (void) pickedColor:(UIColor*)color { // 将【颜色选择控制器】,回调的颜色,设置到控件上,并隐藏 【颜色选择控制器】[self setStrokeColor:color]; _colorPickerCtrl.view.hidden = !_colorPickerCtrl.view.hidden;}// 重要,设置笔刷 新的颜色-(void) setStrokeColor:(UIColor*)newColor{_currentColor = newColor;}// 点击,选择相片按钮-(IBAction) didClickChoosePhoto { // 展现,相片选择控制器 [self addSubview:_imagePickerCtrl.view];}#pragma mark - imagePicker代理方法- (void)imagePickerController:(UIImagePickerController *)pickerdidFinishPickingMediaWithInfo:(NSDictionary *)info{ // 必须手动,关闭照片选择器 [picker.view removeFromSuperview]; // 从info字典得到编辑后的照片【UIImagePickerControllerEditedImage】 _pickedImg = [info valueForKey:@"UIImagePickerControllerOriginalImage"]; // 将图片画到画板上去 [self setNeedsDisplay];}// 【相片选择器】的代理方法,点击取消时,也要隐藏相片选择器- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker{ [_imagePickerCtrl.view removeFromSuperview];}#pragma mark - 核心代码,重要~~~画布上手势处理// 手势开始(画笔落下)// 开始一个新的字典,为每一笔,包括点 和 颜色// Start new dictionary for each touch, with points and color- (void) touchesBegan:(NSSet *) touches withEvent:(UIEvent *) event{ // 一个笔画中的所有点,触摸开始时的【起点】NSMutableArray *pointsArrInOneStroke = [NSMutableArray array];NSMutableDictionary *strokeDict = [NSMutableDictionary dictionary];[strokeDict setObject:pointsArrInOneStroke forKey:@"points"]; // 笔的颜色[strokeDict setObject:_currentColor forKey:@"color"]; // 笔的大小[strokeDict setObject:[NSNumber numberWithFloat:currentSize] forKey:@"size"]; // 落笔点CGPoint point = [[touches anyObject] locationInView:self];[pointsArrInOneStroke addObject:NSStringFromCGPoint(point)];[_strokesArr addObject:strokeDict];}// 将每一个点添加到 点数组// Add each point to points array- (void) touchesMoved:(NSSet *) touches withEvent:(UIEvent *) event{ // 移动后的一个点CGPoint point = [[touches anyObject] locationInView:self]; // 前一个点CGPoint prevPoint = [[touches anyObject] previousLocationInView:self]; // 字典中先前的点数组NSMutableArray *pointsArrInOneStroke = [[_strokesArr lastObject] objectForKey:@"points"]; // 在后面追加 新的点[pointsArrInOneStroke addObject:NSStringFromCGPoint(point)];CGRect rectToRedraw = CGRectMake(\ ((prevPoint.x>point.x)?point.x:prevPoint.x)-currentSize,\ ((prevPoint.y>point.y)?point.y:prevPoint.y)-currentSize,\ fabs(point.x-prevPoint.x)+2*currentSize,\ fabs(point.y-prevPoint.y)+2*currentSize\ );[self setNeedsDisplayInRect:rectToRedraw];}// 手势结束(画笔抬起)// Send over new trace when the touch ends- (void) touchesEnded:(NSSet *) touches withEvent:(UIEvent *) event{[_abandonedStrokesArr removeAllObjects]; // 2.设置重做、撤销、清空三个按钮的状态 [self updateToolBarBtnStatus]; }@end
颜色选择控制器
ColorPickerController
//// ColorPickerController.h// 24_Canvas画画板//// Created by beyond on 14-8-26.// Copyright (c) 2014年 com.beyond. All rights reserved.//#import <UIKit/UIKit.h>@interface ColorPickerController : UIViewController #pragma mark - 属性列表// xib上的imgView@property (nonatomic,retain) IBOutlet UIImageView *imgView;// 代理用weak@property (weak) id pickedColorDelegate;#pragma mark - 方法列表// 核心,根据位图引用 创建基于该位图的上下文对象- (CGContextRef) createARGBBitmapContextFromImage:(CGImageRef)inImage;// 核心,根据触摸点,从上下文中取出对应位置像素点的颜色值- (UIColor*) getPixelColorAtLocation:(CGPoint)point;@end
核心代码
//// ColorPickerController.m// 24_Canvas画画板//// Created by beyond on 14-8-26.// Copyright (c) 2014年 com.beyond. All rights reserved.//#import "ColorPickerController.h"#import "Canvas.h"@implementation ColorPickerController#pragma mark - 点击结束- (void) touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { UITouch* touch = [touches anyObject]; // tap点击的位置CGPoint point = [touch locationInView:self.imgView]; // 1.调用自定义方法,从【点】中取颜色UIColor *selectedColor = [self getPixelColorAtLocation:point]; // 2.告诉代理,解析出来的颜色[_pickedColorDelegate pickedColor:selectedColor];}// 核心代码:关于下面两个方法更多的详细资料,敬请查阅【iOS Developer Library 】#pragma mark - 核心代码,将图片写入内存,再依据【点】中取颜色- (UIColor *) getPixelColorAtLocation:(CGPoint)point{UIColor *color = nil; // 得到取色图片的引用CGImageRef colorImage = _imgView.image.CGImage; // Create off screen bitmap context to draw the image into. Format ARGB is 4 bytes for each pixel: Alpa, Red, Green, Blue // 调用自定义方法:从_imgView里面的image的引用,创建并返回对应的上下文CGContextRef contexRef = [self createARGBBitmapContextFromImage:colorImage]; // 如果创建该图片对应的上下文失败if (contexRef == NULL){ NSLog(@"取色图片--创建对应的上下文失败~"); return nil; }// 准备将【取色图片】写入刚才创建出来的上下文 size_t w = CGImageGetWidth(colorImage);// problem!size_t h = CGImageGetHeight(colorImage);CGRect rect = {{0,0},{w,h}}; log_rect(rect) // 调试输出rect:--{{0, 0}, {225, 250}} int bytesPerRow = CGBitmapContextGetBytesPerRow(contexRef); log_int(bytesPerRow) //调试输出int:--900 // Draw the image to the bitmap context. Once we draw, the memory // allocated for the context for rendering will then contain the // raw image data in the specified color space. // 将位图写入(渲染)已经分配好的内存区域CGContextDrawImage(contexRef, rect, colorImage); // 得到位图上下文 内存数据块的首地址,用指针记住,作为基地址unsigned char* dataPoint = CGBitmapContextGetData (contexRef); NSLog(@"----首地址,指针%p",dataPoint); // ----首地址,指针0x8b3f000 if (dataPoint != NULL) {//offset 即:根据触摸点的xy,定位到位图内存空间中的一个特定像素//4 的意思是每一个像素点,占4个字节 // w是每一行所有点的总数 // 根据所在行,所在列,算出在内存块中的偏移地址,然后乘以4,因为每一个点在内存中占四个字节int offset = 4*((w*round(point.y))+round(point.x)); // alpha 为内存基地址+偏移地址int alpha = dataPoint[offset]; // red 为内存基地址+偏移地址+1 其他类似int red = dataPoint[offset+1];int green = dataPoint[offset+2];int blue = dataPoint[offset+3]; NSLog(@"偏移地址: %i colors: RGBA %i %i %i %i",offset,red,green,blue,alpha); // offset: 150908 colors: RGB A 255 0 254 255 // 根据RGBA 生成颜色对象color = [UIColor colorWithRed:(red/255.0f) green:(green/255.0f) blue:(blue/255.0f) alpha:(alpha/255.0f)];}// 操作完成后,释放上下文对象CGContextRelease(contexRef); // 从内存中释放掉 加载到内存的图像数据if (dataPoint) { free(dataPoint); }return color;}// 自定义方法2:通过_imgView里面的image的引用,创建并返回对应的上下文- (CGContextRef) createARGBBitmapContextFromImage:(CGImageRef) inImage{ // 要创建的上下文CGContextRef context = NULL; // 色彩空间CGColorSpaceRef colorSpace; // 位图数据在内存空间的首地址void * bitmapData; // 每一行的字节数int bitmapBytesPerRow; // 图片总的占的字节数int bitmapByteCount; // 得到图片的宽度和高度,将要使用整个图片,创建上下文size_t pixelsWide = CGImageGetWidth(inImage);size_t pixelsHigh = CGImageGetHeight(inImage);// 每一行占多少字节. 本取色图片中的每一个像素点占4个字节; // 红 绿 蓝 透明度 各占一个字节(8位 取值范围0~255) // 每一行的字节数,因为每一个像素点占4个字节(包含RGBA)(其中一个R就是一个字节,占8位,取值是2的8次方 0~255)bitmapBytesPerRow = (pixelsWide * 4); // 图片总的占的字节数bitmapByteCount = (bitmapBytesPerRow * pixelsHigh);// 使用指定的 色彩空间(RGB)colorSpace = CGColorSpaceCreateDeviceRGB();if (colorSpace == NULL){fprintf(stderr, "创建并分配色彩空间 出错\n");return NULL;}// This is the destination in memory// where any drawing to the bitmap context will be rendered. // 为取色图片数据 分配所有的内存空间 // 所有画到取色图片上下文的操作,都将被渲染到此内存空间bitmapData = malloc( bitmapByteCount );if (bitmapData == NULL) {fprintf (stderr, "内存空间分配失败~");CGColorSpaceRelease( colorSpace );return NULL;}// 创建位图上下文. 使用 pre-multiplied ARGB, ARGB中的每一个成员都占8个bit位,即一字节,一个像素共占4个字节// 无论原取色图片的格式是什么(CMYK或Grayscale),都将通过CGBitmapContextCreate方法,转成指定的ARGB格式context = CGBitmapContextCreate (bitmapData, pixelsWide, pixelsHigh, 8, // bits per component bitmapBytesPerRow, colorSpace, kCGImageAlphaPremultipliedFirst);if (context == NULL){free (bitmapData);fprintf (stderr, "位图上下文创建失败~");}// 在返回上下文之前 必须记得释放 色彩空间CGColorSpaceRelease( colorSpace );return context;}@end
1 0
- iOS_24_画画板(含取色板)
- 画画板
- 画画板
- 画画板
- android画画板
- 图片画画板
- 图片画画板
- iOS 画画板
- android 画画板
- 画画板原理
- andorid画画板
- 图片画画板
- Android画画板剖析
- android_65_简易画画板
- Android 画画板
- 画画板案例
- Android画画板
- 画画
- 嵌入式 栈溢出的几种情况
- 3.2.5 building machine learning system in python page 42
- 输入一串只有由1-9对应数字的拼音输出对应的数字如输入为yiersan输出为:123
- 【转】YV12 and NV12
- c++常见面试题30道
- iOS_24_画画板(含取色板)
- PCB器件的布局理
- linux环境下,vim粘贴时缩进错乱解决
- mysql skyform mark
- Oracle数据库创建表空间、创建用户指定表空间GOOD
- 麻雀虽小,五脏俱全:JSR311让Restful WebService变简单
- Linux 常用命令学习笔记(一)
- POJ 3090 Visible Lattice Points
- OpenCV中IplImage图像格式与BYTE图像数据的转换