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

Автор оригинала: 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.  
  3.   NSImage * img = [self image];
  4.   
  5.   CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
  6.   NSRect selfRect = [self bounds];
  7.   
  8.   if (!img)
  9.   {
  10.     static NSString * idleText = @"Just drag image here!";
  11.     
  12.     NSFont * myFont = [NSFont fontWithName:@"Arial" size:16];
  13.     NSMutableParagraphStyle * ps = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
  14.     [ps setAlignment:NSCenterTextAlignment];
  15.     
  16.     NSDictionary * attsDict = [NSDictionary dictionaryWithObjectsAndKeys:
  17.                   [NSColor blackColor], NSForegroundColorAttributeName,
  18.                   myFont, NSFontAttributeName,
  19.                   nil ];
  20.     
  21.     NSSize textSize = [idleText sizeWithAttributes:attsDict];
  22.     
  23.     [idleText drawAtPoint:NSMakePoint((selfRect.size.width - textSize.width)/2, (selfRect.size.height - textSize.height)/2) withAttributes:attsDict];
  24.     return;
  25.   }
  26.   
  27.   //calculate aspect ratios for correct draw lines of slicing
  28.   float aspectRatioImg  = [img size].width/[img size].height;
  29.   float aspectRatioView  = selfRect.size.width/selfRect.size.height;
  30.   NSRect scaledRect = selfRect;
  31.     
  32.   if (aspectRatioImg > aspectRatioView)
  33.   {
  34.     scaledRect.size.height = scaledRect.size.width/aspectRatioImg;
  35.     scaledRect.origin.y = (selfRect.size.height - scaledRect.size.height)/2;
  36.   }
  37.   else
  38.   {
  39.     scaledRect.size.width = scaledRect.size.height*aspectRatioImg;    
  40.     scaledRect.origin.x = (selfRect.size.width - scaledRect.size.width)/2;
  41.   }
  42.   
  43.   //set line color and width
  44.   CGContextSetRGBStrokeColor(context, 1.f,1.f,0.f,1.f);
  45.   CGContextSetLineWidth(context, 1.f);
  46.   
  47.   //draw lines
  48.   CGContextBeginPath(context);
  49.   
  50.   //vertical lines
  51.   for (int i=1;i<hSize;i++)
  52.   {
  53.     int x = scaledRect.origin.x + i*scaledRect.size.width/hSize;
  54.     CGContextMoveToPoint(context, x, scaledRect.origin.y);
  55.     CGContextAddLineToPoint(context, x, scaledRect.origin.y + scaledRect.size.height);
  56.   }
  57.   //horizontal lines
  58.   for (int j=1;j<vSize;j++)
  59.   {
  60.     int y = scaledRect.origin.y + j*scaledRect.size.height/vSize;
  61.     CGContextMoveToPoint(context, scaledRect.origin.x, y);
  62.     CGContextAddLineToPoint(context, scaledRect.origin.x + scaledRect.size.width, y);    
  63.   }
  64.   CGContextClosePath(context);
  65.   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.  
  3. - (id) initWithImage:(NSImage *) anImage rect:(NSRect) aRect url:(NSURL*) anUrl
  4. {
  5.   self = [super init];   
  6.   if (self)
  7.   {
  8.     image  = [anImage retain];
  9.     rect  = aRect;
  10.     url    = [anUrl copy];
  11.   }
  12.   return self;  
  13. }
  14.  
  15. -(void) main
  16. {
  17.   NSImage *target = [[NSImage alloc] initWithSize:NSMakeSize(rect.size.width,rect.size.height)];
  18.   
  19.   
  20.   [target lockFocus];
  21.   [image drawInRect:NSMakeRect(0,0,rect.size.width,rect.size.height)
  22.        fromRect:rect
  23.       operation:NSCompositeCopy
  24.        fraction:1.0];
  25.   [target unlockFocus];
  26.   
  27.   
  28.   NSData *imageData = [target TIFFRepresentation];
  29.   
  30.   NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:imageData];
  31.   
  32.   NSDictionary *imageProps = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:1.0] forKey:NSImageCompressionFactor];
  33.   
  34.   imageData = [imageRep representationUsingType:NSJPEGFileType properties:imageProps];
  35.     
  36.   //write the data to file
  37.   [imageData writeToURL: url atomically: NO];
  38.   
  39.   [target release];
  40. }
  41.  
  42. -(void) dealloc
  43. {
  44.   [image release];
  45.   [url release];
  46.   [super dealloc];
  47. }
  48.  
  49. @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.   
  8.   IBOutlet NSProgressIndicator * progressIndicator;
  9.     
  10.   NSOperationQueue * queue;
  11. }
  12. -(IBAction) startButtonPressed:(id)sender;
  13.  
  14. //bind slider with label
  15. -(IBAction) hPartsSliderMoved:(id)sender;
  16. -(IBAction) vPartsSliderMoved:(id)sender;
  17. -(void) splitImage:(NSArray*) anArray;
  18. @end
* This source code was highlighted with Source Code Highlighter.


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


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

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


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

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

Исходный код проекта можно скачать здесь.
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 10

    +1
    bash + ImageMagick — наше всё!
      0
      ну, не отказался бы от примера с bash + ImageMagick.
      когда искал подобное, ничего не нашёл.
      0
      Ты как думаешь, что проще обычному пользователю, а не продвинутому айтишнику?
        0
        о чём вопрос? если о том, что проще использовать, то отвечу — утилиту.
          0
          ИМХО, писать несколько сотен строк кода на Objective-C явно не проще, чем десяток на bash.
        0
        Выбор числа ползунком по-моему не очень удобен.
        Картинки в итоге получатся обинакового размера… только в первоначальную картинку их сложить не получится ;-)
        Если не подбирать специально кратные размеры, то мы теряем по ширине и высоте столько пикселей, на сколько частей делим (из-за отброса дробной части). Причём теряем не равномерно, а с краю.

        Когда же вы поймёте, что компилируется — не значит работает!
          +2
          С замечанием согласен, но разрезать рисунок например 171х155 на 4 части — по крайней мере, нелогично.
          Да и задача была — резать текстуру на более мелкие части для подгрузки частей по ходу работы.
          А у текстур как раз размеры кратны двойке.

          И не надо высказываться в столь резкой форме. Я всё понял достаточно давно. А вам бы пора и мудрости набраться, не подросток ведь уже.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое