28 April 2010

Пишем утилиту для разрезания картинок

Development for iOS
Translation
Original author: MLS-Automatization
Недавно мне понадобилась утилита для разрезки изображения на маленькие кусочки одинакового размера, но все поиски успехом не увенчались. После этого в голову пришла старая добрая мысль — «хочешь что-то сделать хорошо, сделай это сам» и было принято решения о написании крошечной утилитки.
Итак, приступим.


Первое, что нам понадобится — кастомный image view для отображения рисунка с линиями разреза.

  1. @interface MyImageView : NSImageView
  2. {
  3. int vSize;
  4. int hSize;
  5. }
  6. @property int vSize;
  7. @property int hSize;
  8. @end
* This source code was highlighted with Source Code Highlighter.


Перейдём к реализации методов класса MyImageView. Переопределим метод — (id)initWithCoder:(NSCoder *) coder для кастомизации создания экземляра класса.

  1. if (self = [super initWithCoder:coder])
  2. {
  3. [self setEditable:YES];
  4. hSize = 1;
  5. vSize = 1;
  6. }
  7. return self;
* This source code was highlighted with Source Code Highlighter.


Далее, переопределим метод — (void)drawRect:(NSRect)dirtyRect для отображения линий разреза.

  1. [super drawRect:dirtyRect];
  2. NSImage * img = [self image];
  3. CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
  4. NSRect selfRect = [self bounds];
  5. if (!img)
  6. {
  7. static NSString * idleText = @"Just drag image here!";
  8. NSFont * myFont = [NSFont fontWithName:@"Arial" size:16];
  9. NSMutableParagraphStyle * ps = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
  10. [ps setAlignment:NSCenterTextAlignment];
  11. NSDictionary * attsDict = [NSDictionary dictionaryWithObjectsAndKeys:
  12. [NSColor blackColor], NSForegroundColorAttributeName,
  13. myFont, NSFontAttributeName,
  14. nil ];
  15. NSSize textSize = [idleText sizeWithAttributes:attsDict];
  16. [idleText drawAtPoint:NSMakePoint((selfRect.size.width - textSize.width)/2, (selfRect.size.height - textSize.height)/2) withAttributes:attsDict];
  17. return;
  18. }
  19. //calculate aspect ratios for correct draw lines of slicing
  20. float aspectRatioImg = [img size].width/[img size].height;
  21. float aspectRatioView = selfRect.size.width/selfRect.size.height;
  22. NSRect scaledRect = selfRect;
  23. if (aspectRatioImg > aspectRatioView)
  24. {
  25. scaledRect.size.height = scaledRect.size.width/aspectRatioImg;
  26. scaledRect.origin.y = (selfRect.size.height - scaledRect.size.height)/2;
  27. }
  28. else
  29. {
  30. scaledRect.size.width = scaledRect.size.height*aspectRatioImg;
  31. scaledRect.origin.x = (selfRect.size.width - scaledRect.size.width)/2;
  32. }
  33. //set line color and width
  34. CGContextSetRGBStrokeColor(context, 1.f,1.f,0.f,1.f);
  35. CGContextSetLineWidth(context, 1.f);
  36. //draw lines
  37. CGContextBeginPath(context);
  38. //vertical lines
  39. for (int i=1;i<hSize;i++)
  40. {
  41. int x = scaledRect.origin.x + i*scaledRect.size.width/hSize;
  42. CGContextMoveToPoint(context, x, scaledRect.origin.y);
  43. CGContextAddLineToPoint(context, x, scaledRect.origin.y + scaledRect.size.height);
  44. }
  45. //horizontal lines
  46. for (int j=1;j<vSize;j++)
  47. {
  48. int y = scaledRect.origin.y + j*scaledRect.size.height/vSize;
  49. CGContextMoveToPoint(context, scaledRect.origin.x, y);
  50. CGContextAddLineToPoint(context, scaledRect.origin.x + scaledRect.size.width, y);
  51. }
  52. CGContextClosePath(context);
  53. CGContextStrokePath(context);
* This source code was highlighted with Source Code Highlighter.


Не забудьте прописать synthesize для всех свойств!

Для быстрого разрезания большой картинки мы сделаем наше приложения многопоточным, тем более, что MacOS X предоставляет достаточно много средств для этого. Мы же воспользуемся классом NSOperation, который появился в MacOS X, начиная с версии 10.5. NSOperation — многопоточность проще некуда.

Создадим класс-наследник NSOperation и переопределим метод -main. Он-то вызывается при запуске операции. Инерфейс класса:

  1. @interface ImageSliceOperation : NSOperation
  2. {
  3. NSImage * image; //big image
  4. NSRect rect; //rect of slice
  5. NSURL * url; //URL to save slice
  6. }
  7. - (id) initWithImage:(NSImage *) anImage rect:(NSRect) aRect url:(NSURL*) anUrl;
  8. - (void) main;
  9. @end
* This source code was highlighted with Source Code Highlighter.


И реализация класса:
  1. @implementation ImageSliceOperation
  2. - (id) initWithImage:(NSImage *) anImage rect:(NSRect) aRect url:(NSURL*) anUrl
  3. {
  4. self = [super init];
  5. if (self)
  6. {
  7. image = [anImage retain];
  8. rect = aRect;
  9. url = [anUrl copy];
  10. }
  11. return self;
  12. }
  13. -(void) main
  14. {
  15. NSImage *target = [[NSImage alloc] initWithSize:NSMakeSize(rect.size.width,rect.size.height)];
  16. [target lockFocus];
  17. [image drawInRect:NSMakeRect(0,0,rect.size.width,rect.size.height)
  18. fromRect:rect
  19. operation:NSCompositeCopy
  20. fraction:1.0];
  21. [target unlockFocus];
  22. NSData *imageData = [target TIFFRepresentation];
  23. NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:imageData];
  24. NSDictionary *imageProps = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:1.0] forKey:NSImageCompressionFactor];
  25. imageData = [imageRep representationUsingType:NSJPEGFileType properties:imageProps];
  26. //write the data to file
  27. [imageData writeToURL: url atomically: NO];
  28. [target release];
  29. }
  30. -(void) dealloc
  31. {
  32. [image release];
  33. [url release];
  34. [super dealloc];
  35. }
  36. @end
* This source code was highlighted with Source Code Highlighter.


Теперь осталось создать класс MainView.

  1. @interface MainView : NSView
  2. {
  3. IBOutlet MyImageView * imageView;
  4. IBOutlet NSTextField * hPartsLabel;
  5. IBOutlet NSTextField * vPartsLabel;
  6. IBOutlet NSButton * startButton;
  7. IBOutlet NSProgressIndicator * progressIndicator;
  8. NSOperationQueue * queue;
  9. }
  10. -(IBAction) startButtonPressed:(id)sender;
  11. //bind slider with label
  12. -(IBAction) hPartsSliderMoved:(id)sender;
  13. -(IBAction) vPartsSliderMoved:(id)sender;
  14. -(void) splitImage:(NSArray*) anArray;
  15. @end
* This source code was highlighted with Source Code Highlighter.


  1. @implementation MainView
  2. -(id) initWithCoder: (NSCoder*) coder
  3. {
  4. self = [super initWithCoder: coder];
  5. if (self)
  6. {
  7. queue = [[NSOperationQueue alloc] init];
  8. [queue setMaxConcurrentOperationCount:4];
  9. }
  10. return self;
  11. }
  12. -(IBAction) startButtonPressed:(id)sender
  13. {
  14. if (![imageView image])
  15. {
  16. NSAlert *alert = [[NSAlert alloc] init];
  17. [alert addButtonWithTitle:@"OK"];
  18. [alert setMessageText:@"Error!"];
  19. [alert setInformativeText:@"You must open file previously."];
  20. [alert setAlertStyle: NSCriticalAlertStyle];
  21. [alert runModal];
  22. [alert release];
  23. return;
  24. }
  25. NSOpenPanel *saveDlg = [NSOpenPanel openPanel]; //select destination
  26. [saveDlg setCanCreateDirectories:YES];
  27. [saveDlg setCanChooseDirectories: YES];
  28. [saveDlg setCanChooseFiles: NO];
  29. int result = [saveDlg runModal];
  30. if(result == NSOKButton)
  31. {
  32. NSURL * dirURL = [saveDlg directoryURL];
  33. NSArray * argArray = [NSArray arrayWithObjects: dirURL,
  34. [imageView image],
  35. [NSNumber numberWithInt:[vPartsLabel intValue]],
  36. [NSNumber numberWithInt:[hPartsLabel intValue]],
  37. nil];
  38. [NSThread detachNewThreadSelector:@selector(splitImage:) toTarget:self withObject:argArray];
  39. }
  40. }
  41. -(IBAction) hPartsSliderMoved:(id)sender
  42. {
  43. int val = [sender intValue];
  44. [hPartsLabel setStringValue:[NSString stringWithFormat:@"%i",val]];
  45. imageView.hSize = val;
  46. [imageView setNeedsDisplay:YES];
  47. }
  48. -(IBAction) vPartsSliderMoved:(id)sender
  49. {
  50. int val = [sender intValue];
  51. [vPartsLabel setStringValue:[NSString stringWithFormat:@"%i",val]];
  52. imageView.vSize = val;
  53. [imageView setNeedsDisplay:YES];
  54. }
  55. -(void) splitImage:(NSArray*) anArray //0 - destination URL; 1 - image; 2 - vParts; 3 - hParts
  56. {
  57. NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
  58. [startButton setEnabled:NO];
  59. [progressIndicator setUsesThreadedAnimation:YES];
  60. [progressIndicator startAnimation:self];
  61. NSURL* anUrl = [anArray objectAtIndex:0];
  62. NSImage* anImage = [anArray objectAtIndex:1];
  63. int vParts = [[anArray objectAtIndex:2] intValue];
  64. int hParts = [[anArray objectAtIndex:3] intValue];
  65. int imgW = [anImage size].width;
  66. int imgH = [anImage size].height;
  67. int partW = imgW/hParts;
  68. int partH = imgH/vParts;
  69. for (int i=0; i<hParts; i++)
  70. {
  71. for (int j=0; j<vParts; j++)
  72. {
  73. int currentX = partW*i;
  74. int currentY = imgH - partH*(j+1);
  75. NSRect rect = NSMakeRect(currentX, currentY, partW, partH);
  76. NSString * fileName = [NSString stringWithFormat:@"%i_%i.jpg",i,j];
  77. NSURL * fileURL = [NSURL URLWithString:fileName relativeToURL:anUrl];
  78. ImageSliceOperation * op = [[ImageSliceOperation alloc] initWithImage:anImage rect:rect url:fileURL];
  79. [queue addOperation:op];
  80. [op release];
  81. }
  82. }
  83. [queue waitUntilAllOperationsAreFinished];
  84. [progressIndicator stopAnimation:self];
  85. [startButton setEnabled:YES];
  86. [pool release];
  87. }
  88. @end
* This source code was highlighted with Source Code Highlighter.


Метод splitImage: вызывается в отдельном потоке для предотвращения зависания интерфейса. Метод создаёт ImageSliceOperation и добавляет в очередь.Также этот метод показывает индикатор прогресса пока происходит основное действо.
Последний шаг в написании кода — создание application delegate.

  1. @implementation iPictureSplitterAppDelegate
  2. - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
  3. {
  4. //for custom init
  5. }
  6. -(IBAction) showAboutPanel:(id)sender
  7. {
  8. [aboutPanel orderFront: self];
  9. }
  10. @end
* This source code was highlighted with Source Code Highlighter.


Утилита практически готова. Осталось лишь создать документ Interface Builder, создать окно, разместить элементы UI на нём и связать их с outlet'ами наших классов.

Результат выглядит так:
image

Исходный код проекта можно скачать здесь.
Tags:mac os xapplexcodeobjective-c
Hubs: Development for iOS
+1
2k 9
Comments 10