Недавно мне понадобилась утилита для разрезки изображения на маленькие кусочки одинакового размера, но все поиски успехом не увенчались. После этого в голову пришла старая добрая мысль — «хочешь что-то сделать хорошо, сделай это сам» и было принято решения о написании крошечной утилитки.
Итак, приступим.
Первое, что нам понадобится — кастомный image view для отображения рисунка с линиями разреза.
Перейдём к реализации методов класса MyImageView. Переопределим метод — (id)initWithCoder:(NSCoder *) coder для кастомизации создания экземляра класса.
Далее, переопределим метод — (void)drawRect:(NSRect)dirtyRect для отображения линий разреза.
Не забудьте прописать synthesize для всех свойств!
Для быстрого разрезания большой картинки мы сделаем наше приложения многопоточным, тем более, что MacOS X предоставляет достаточно много средств для этого. Мы же воспользуемся классом NSOperation, который появился в MacOS X, начиная с версии 10.5. NSOperation — многопоточность проще некуда.
Создадим класс-наследник NSOperation и переопределим метод -main. Он-то вызывается при запуске операции. Инерфейс класса:
И реализация класса:
Теперь осталось создать класс MainView.
Метод splitImage: вызывается в отдельном потоке для предотвращения зависания интерфейса. Метод создаёт ImageSliceOperation и добавляет в очередь.Также этот метод показывает индикатор прогресса пока происходит основное действо.
Последний шаг в написании кода — создание application delegate.
Утилита практически готова. Осталось лишь создать документ Interface Builder, создать окно, разместить элементы UI на нём и связать их с outlet'ами наших классов.
Результат выглядит так:
Исходный код проекта можно скачать здесь.
Итак, приступим.
Первое, что нам понадобится — кастомный image view для отображения рисунка с линиями разреза.
- @interface MyImageView : NSImageView
- {
- int vSize;
- int hSize;
- }
- @property int vSize;
- @property int hSize;
- @end
* This source code was highlighted with Source Code Highlighter.
Перейдём к реализации методов класса MyImageView. Переопределим метод — (id)initWithCoder:(NSCoder *) coder для кастомизации создания экземляра класса.
- if (self = [super initWithCoder:coder])
- {
- [self setEditable:YES];
- hSize = 1;
- vSize = 1;
- }
- return self;
* This source code was highlighted with Source Code Highlighter.
Далее, переопределим метод — (void)drawRect:(NSRect)dirtyRect для отображения линий разреза.
- [super drawRect:dirtyRect];
-
- NSImage * img = [self image];
-
- CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
- NSRect selfRect = [self bounds];
-
- if (!img)
- {
- static NSString * idleText = @"Just drag image here!";
-
- NSFont * myFont = [NSFont fontWithName:@"Arial" size:16];
- NSMutableParagraphStyle * ps = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
- [ps setAlignment:NSCenterTextAlignment];
-
- NSDictionary * attsDict = [NSDictionary dictionaryWithObjectsAndKeys:
- [NSColor blackColor], NSForegroundColorAttributeName,
- myFont, NSFontAttributeName,
- nil ];
-
- NSSize textSize = [idleText sizeWithAttributes:attsDict];
-
- [idleText drawAtPoint:NSMakePoint((selfRect.size.width - textSize.width)/2, (selfRect.size.height - textSize.height)/2) withAttributes:attsDict];
- return;
- }
-
- //calculate aspect ratios for correct draw lines of slicing
- float aspectRatioImg = [img size].width/[img size].height;
- float aspectRatioView = selfRect.size.width/selfRect.size.height;
- NSRect scaledRect = selfRect;
-
- if (aspectRatioImg > aspectRatioView)
- {
- scaledRect.size.height = scaledRect.size.width/aspectRatioImg;
- scaledRect.origin.y = (selfRect.size.height - scaledRect.size.height)/2;
- }
- else
- {
- scaledRect.size.width = scaledRect.size.height*aspectRatioImg;
- scaledRect.origin.x = (selfRect.size.width - scaledRect.size.width)/2;
- }
-
- //set line color and width
- CGContextSetRGBStrokeColor(context, 1.f,1.f,0.f,1.f);
- CGContextSetLineWidth(context, 1.f);
-
- //draw lines
- CGContextBeginPath(context);
-
- //vertical lines
- for (int i=1;i<hSize;i++)
- {
- int x = scaledRect.origin.x + i*scaledRect.size.width/hSize;
- CGContextMoveToPoint(context, x, scaledRect.origin.y);
- CGContextAddLineToPoint(context, x, scaledRect.origin.y + scaledRect.size.height);
- }
- //horizontal lines
- for (int j=1;j<vSize;j++)
- {
- int y = scaledRect.origin.y + j*scaledRect.size.height/vSize;
- CGContextMoveToPoint(context, scaledRect.origin.x, y);
- CGContextAddLineToPoint(context, scaledRect.origin.x + scaledRect.size.width, y);
- }
- CGContextClosePath(context);
- CGContextStrokePath(context);
* This source code was highlighted with Source Code Highlighter.
Не забудьте прописать synthesize для всех свойств!
Для быстрого разрезания большой картинки мы сделаем наше приложения многопоточным, тем более, что MacOS X предоставляет достаточно много средств для этого. Мы же воспользуемся классом NSOperation, который появился в MacOS X, начиная с версии 10.5. NSOperation — многопоточность проще некуда.
Создадим класс-наследник NSOperation и переопределим метод -main. Он-то вызывается при запуске операции. Инерфейс класса:
- @interface ImageSliceOperation : NSOperation
- {
- NSImage * image; //big image
- NSRect rect; //rect of slice
- NSURL * url; //URL to save slice
- }
- - (id) initWithImage:(NSImage *) anImage rect:(NSRect) aRect url:(NSURL*) anUrl;
- - (void) main;
- @end
* This source code was highlighted with Source Code Highlighter.
И реализация класса:
- @implementation ImageSliceOperation
-
- - (id) initWithImage:(NSImage *) anImage rect:(NSRect) aRect url:(NSURL*) anUrl
- {
- self = [super init];
- if (self)
- {
- image = [anImage retain];
- rect = aRect;
- url = [anUrl copy];
- }
- return self;
- }
-
- -(void) main
- {
- NSImage *target = [[NSImage alloc] initWithSize:NSMakeSize(rect.size.width,rect.size.height)];
-
-
- [target lockFocus];
- [image drawInRect:NSMakeRect(0,0,rect.size.width,rect.size.height)
- fromRect:rect
- operation:NSCompositeCopy
- fraction:1.0];
- [target unlockFocus];
-
-
- NSData *imageData = [target TIFFRepresentation];
-
- NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:imageData];
-
- NSDictionary *imageProps = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:1.0] forKey:NSImageCompressionFactor];
-
- imageData = [imageRep representationUsingType:NSJPEGFileType properties:imageProps];
-
- //write the data to file
- [imageData writeToURL: url atomically: NO];
-
- [target release];
- }
-
- -(void) dealloc
- {
- [image release];
- [url release];
- [super dealloc];
- }
-
- @end
* This source code was highlighted with Source Code Highlighter.
Теперь осталось создать класс MainView.
- @interface MainView : NSView
- {
- IBOutlet MyImageView * imageView;
- IBOutlet NSTextField * hPartsLabel;
- IBOutlet NSTextField * vPartsLabel;
- IBOutlet NSButton * startButton;
-
- IBOutlet NSProgressIndicator * progressIndicator;
-
- NSOperationQueue * queue;
- }
- -(IBAction) startButtonPressed:(id)sender;
-
- //bind slider with label
- -(IBAction) hPartsSliderMoved:(id)sender;
- -(IBAction) vPartsSliderMoved:(id)sender;
- -(void) splitImage:(NSArray*) anArray;
- @end
* This source code was highlighted with Source Code Highlighter.
- @implementation MainView
-
- -(id) initWithCoder: (NSCoder*) coder
- {
- self = [super initWithCoder: coder];
- if (self)
- {
- queue = [[NSOperationQueue alloc] init];
- [queue setMaxConcurrentOperationCount:4];
- }
- return self;
- }
-
- -(IBAction) startButtonPressed:(id)sender
- {
- if (![imageView image])
- {
- NSAlert *alert = [[NSAlert alloc] init];
- [alert addButtonWithTitle:@"OK"];
- [alert setMessageText:@"Error!"];
- [alert setInformativeText:@"You must open file previously."];
- [alert setAlertStyle: NSCriticalAlertStyle];
- [alert runModal];
- [alert release];
- return;
- }
- NSOpenPanel *saveDlg = [NSOpenPanel openPanel]; //select destination
- [saveDlg setCanCreateDirectories:YES];
- [saveDlg setCanChooseDirectories: YES];
- [saveDlg setCanChooseFiles: NO];
- int result = [saveDlg runModal];
- if(result == NSOKButton)
- {
- NSURL * dirURL = [saveDlg directoryURL];
- NSArray * argArray = [NSArray arrayWithObjects: dirURL,
- [imageView image],
- [NSNumber numberWithInt:[vPartsLabel intValue]],
- [NSNumber numberWithInt:[hPartsLabel intValue]],
- nil];
- [NSThread detachNewThreadSelector:@selector(splitImage:) toTarget:self withObject:argArray];
- }
- }
-
- -(IBAction) hPartsSliderMoved:(id)sender
- {
- int val = [sender intValue];
- [hPartsLabel setStringValue:[NSString stringWithFormat:@"%i",val]];
- imageView.hSize = val;
- [imageView setNeedsDisplay:YES];
- }
-
- -(IBAction) vPartsSliderMoved:(id)sender
- {
- int val = [sender intValue];
- [vPartsLabel setStringValue:[NSString stringWithFormat:@"%i",val]];
- imageView.vSize = val;
- [imageView setNeedsDisplay:YES];
- }
-
- -(void) splitImage:(NSArray*) anArray //0 - destination URL; 1 - image; 2 - vParts; 3 - hParts
- {
- NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
-
- [startButton setEnabled:NO];
- [progressIndicator setUsesThreadedAnimation:YES];
- [progressIndicator startAnimation:self];
-
- NSURL* anUrl = [anArray objectAtIndex:0];
- NSImage* anImage = [anArray objectAtIndex:1];
- int vParts = [[anArray objectAtIndex:2] intValue];
- int hParts = [[anArray objectAtIndex:3] intValue];
-
- int imgW = [anImage size].width;
- int imgH = [anImage size].height;
-
- int partW = imgW/hParts;
- int partH = imgH/vParts;
-
- for (int i=0; i<hParts; i++)
- {
- for (int j=0; j<vParts; j++)
- {
- int currentX = partW*i;
- int currentY = imgH - partH*(j+1);
- NSRect rect = NSMakeRect(currentX, currentY, partW, partH);
- NSString * fileName = [NSString stringWithFormat:@"%i_%i.jpg",i,j];
- NSURL * fileURL = [NSURL URLWithString:fileName relativeToURL:anUrl];
-
- ImageSliceOperation * op = [[ImageSliceOperation alloc] initWithImage:anImage rect:rect url:fileURL];
- [queue addOperation:op];
- [op release];
- }
- }
-
- [queue waitUntilAllOperationsAreFinished];
- [progressIndicator stopAnimation:self];
-
- [startButton setEnabled:YES];
-
- [pool release];
- }
-
- @end
* This source code was highlighted with Source Code Highlighter.
Метод splitImage: вызывается в отдельном потоке для предотвращения зависания интерфейса. Метод создаёт ImageSliceOperation и добавляет в очередь.Также этот метод показывает индикатор прогресса пока происходит основное действо.
Последний шаг в написании кода — создание application delegate.
- @implementation iPictureSplitterAppDelegate
-
-
- - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
- {
- //for custom init
- }
-
- -(IBAction) showAboutPanel:(id)sender
- {
- [aboutPanel orderFront: self];
- }
-
- @end
* This source code was highlighted with Source Code Highlighter.
Утилита практически готова. Осталось лишь создать документ Interface Builder, создать окно, разместить элементы UI на нём и связать их с outlet'ами наших классов.
Результат выглядит так:
Исходный код проекта можно скачать здесь.