OSDN Git Service

import jp-0.9.3
[handbrake-jp/handbrake-jp.git] / macosx / PictureController.mm
1 /* $Id: PictureController.mm,v 1.11 2005/08/01 15:10:44 titer Exp $
2
3    This file is part of the HandBrake source code.
4    Homepage: <http://handbrake.fr/>.
5    It may be used under the terms of the GNU General Public License. */
6
7 #import "PictureController.h"
8
9 @interface PictureController (Private)
10
11 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize;
12 - (void)resizeSheetForViewSize: (NSSize)viewSize;
13 - (void)setViewSize: (NSSize)viewSize;
14 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize;
15
16 @end
17
18 @implementation PictureController
19
20 - (id)initWithDelegate:(id)del
21 {
22         if (self = [super initWithWindowNibName:@"PictureSettings"])
23         {
24         // NSWindowController likes to lazily load its window. However since
25         // this controller tries to set all sorts of outlets before the window
26         // is displayed, we need it to load immediately. The correct way to do
27         // this, according to the documentation, is simply to invoke the window
28         // getter once.
29         //
30         // If/when we switch a lot of this stuff to bindings, this can probably
31         // go away.
32         [self window];
33
34                 delegate = del;
35         fPicturePreviews = [[NSMutableDictionary dictionaryWithCapacity: HB_NUM_HBLIB_PICTURES] retain];
36         }
37         return self;
38 }
39
40 - (void) dealloc
41 {
42     [fPicturePreviews release];
43     [super dealloc];
44 }
45
46 - (void) SetHandle: (hb_handle_t *) handle
47 {
48     fHandle = handle;
49
50     [fWidthStepper  setValueWraps: NO];
51     [fWidthStepper  setIncrement: 16];
52     [fWidthStepper  setMinValue: 64];
53     [fHeightStepper setValueWraps: NO];
54     [fHeightStepper setIncrement: 16];
55     [fHeightStepper setMinValue: 64];
56
57     [fCropTopStepper    setIncrement: 2];
58     [fCropTopStepper    setMinValue:  0];
59     [fCropBottomStepper setIncrement: 2];
60     [fCropBottomStepper setMinValue:  0];
61     [fCropLeftStepper   setIncrement: 2];
62     [fCropLeftStepper   setMinValue:  0];
63     [fCropRightStepper  setIncrement: 2];
64     [fCropRightStepper  setMinValue:  0];
65 }
66
67 - (void) SetTitle: (hb_title_t *) title
68 {
69     hb_job_t * job = title->job;
70
71     fTitle = title;
72
73     [fWidthStepper      setMaxValue: title->width];
74     [fWidthStepper      setIntValue: job->width];
75     [fWidthField        setIntValue: job->width];
76     [fHeightStepper     setMaxValue: title->height];
77     [fHeightStepper     setIntValue: job->height];
78     [fHeightField       setIntValue: job->height];
79     [fRatioCheck        setState:    job->keep_ratio ? NSOnState : NSOffState];
80     [fCropTopStepper    setMaxValue: title->height/2-2];
81     [fCropBottomStepper setMaxValue: title->height/2-2];
82     [fCropLeftStepper   setMaxValue: title->width/2-2];
83     [fCropRightStepper  setMaxValue: title->width/2-2];
84
85     /* Populate the Anamorphic NSPopUp button here */
86     [fAnamorphicPopUp removeAllItems];
87     [fAnamorphicPopUp addItemWithTitle: NSLocalizedStringFromTable(@"None", @"Picture", @"")];
88     [fAnamorphicPopUp addItemWithTitle: NSLocalizedStringFromTable(@"Strict", @"Picture", @"")];
89     if (allowLooseAnamorphic)
90     {
91     [fAnamorphicPopUp addItemWithTitle: NSLocalizedStringFromTable(@"Loose", @"Picture", @"")];
92     }
93     [fAnamorphicPopUp selectItemAtIndex: job->pixel_ratio];
94     
95     /* We initially set the previous state of keep ar to on */
96     keepAspectRatioPreviousState = 1;
97         if (!autoCrop)
98         {
99         [fCropMatrix  selectCellAtRow: 1 column:0];
100         /* If auto, lets set the crop steppers according to current job->crop values */
101         [fCropTopStepper    setIntValue: job->crop[0]];
102         [fCropTopField      setIntValue: job->crop[0]];
103         [fCropBottomStepper setIntValue: job->crop[1]];
104         [fCropBottomField   setIntValue: job->crop[1]];
105         [fCropLeftStepper   setIntValue: job->crop[2]];
106         [fCropLeftField     setIntValue: job->crop[2]];
107         [fCropRightStepper  setIntValue: job->crop[3]];
108         [fCropRightField    setIntValue: job->crop[3]];
109         }
110         else
111         {
112         [fCropMatrix  selectCellAtRow: 0 column:0];
113         }
114         
115         /* Set filters widgets according to the filters struct */
116         [fDetelecineCheck setState:fPictureFilterSettings.detelecine];
117     [fDeinterlacePopUp selectItemAtIndex: fPictureFilterSettings.deinterlace];
118     [fDenoisePopUp selectItemAtIndex: fPictureFilterSettings.denoise];
119     [fDeblockCheck setState: fPictureFilterSettings.deblock];
120     
121     fPicture = 0;
122     MaxOutputWidth = title->width - job->crop[2] - job->crop[3];
123     MaxOutputHeight = title->height - job->crop[0] - job->crop[1];
124     [self SettingsChanged: nil];
125 }
126
127 /* we use this to setup the initial picture filters upon first launch, after that their states
128 are maintained across different sources */
129 - (void) setInitialPictureFilters
130 {
131         /* we use a popup to show the deinterlace settings */
132         [fDeinterlacePopUp removeAllItems];
133     [fDeinterlacePopUp addItemWithTitle: NSLocalizedStringFromTable(@"None", @"Picture", @"")];
134     [fDeinterlacePopUp addItemWithTitle: NSLocalizedStringFromTable(@"Fast", @"Picture", @"")];
135     [fDeinterlacePopUp addItemWithTitle: NSLocalizedStringFromTable(@"Slow", @"Picture", @"")];
136         [fDeinterlacePopUp addItemWithTitle: NSLocalizedStringFromTable(@"Slower", @"Picture", @"")];
137     
138         /* Set deinterlaces level according to the integer in the main window */
139         [fDeinterlacePopUp selectItemAtIndex: fPictureFilterSettings.deinterlace];
140
141         /* we use a popup to show the denoise settings */
142         [fDenoisePopUp removeAllItems];
143     [fDenoisePopUp addItemWithTitle: NSLocalizedStringFromTable(@"None", @"Picture", @"")];
144     [fDenoisePopUp addItemWithTitle: NSLocalizedStringFromTable(@"Weak", @"Picture", @"")];
145         [fDenoisePopUp addItemWithTitle: NSLocalizedStringFromTable(@"Medium", @"Picture", @"")];
146     [fDenoisePopUp addItemWithTitle: NSLocalizedStringFromTable(@"Strong", @"Picture", @"")];
147         /* Set denoises level according to the integer in the main window */
148         [fDenoisePopUp selectItemAtIndex: fPictureFilterSettings.denoise];
149     
150     /* we use a popup to show the decomb settings */
151         [fDecombPopUp removeAllItems];
152     [fDecombPopUp addItemWithTitle: NSLocalizedStringFromTable(@"None", @"Picture", @"")];
153     [fDecombPopUp addItemWithTitle: NSLocalizedStringFromTable(@"Default", @"Picture", @"")];
154     [fDecombPopUp addItemWithTitle: NSLocalizedStringFromTable(@"Custom", @"Picture", @"")];
155         /* Set denoises level according to the integer in the main window */
156         [fDecombPopUp selectItemAtIndex: fPictureFilterSettings.decomb];
157
158 }
159
160 // Adjusts the window to draw the current picture (fPicture) adjusting its size as
161 // necessary to display as much of the picture as possible.
162 - (void) displayPreview
163 {
164     [fPictureView setImage: [self imageForPicture: fPicture]];
165         
166         NSSize displaySize = NSMakeSize( ( CGFloat )fTitle->width, ( CGFloat )fTitle->height );
167     /* Set the picture size display fields below the Preview Picture*/
168     if( fTitle->job->pixel_ratio == 1 ) // Original PAR Implementation
169     {
170         output_width = fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3];
171         output_height = fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1];
172         display_width = output_width * fTitle->job->pixel_aspect_width / fTitle->job->pixel_aspect_height;
173         [fInfoField setStringValue:[NSString stringWithFormat:
174                                     NSLocalizedStringFromTable(@"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d", @"Picture", @""),
175                                     fTitle->width, fTitle->height, output_width, output_height, display_width, output_height]];
176         displaySize.width *= ( ( CGFloat )fTitle->job->pixel_aspect_width ) / ( ( CGFloat )fTitle->job->pixel_aspect_height );   
177     }
178     else if (fTitle->job->pixel_ratio == 2) // Loose Anamorphic
179     {
180         display_width = output_width * output_par_width / output_par_height;
181         [fInfoField setStringValue:[NSString stringWithFormat:
182                                     NSLocalizedStringFromTable(@"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d", @"Picture", @""),
183                                     fTitle->width, fTitle->height, output_width, output_height, display_width, output_height]];
184         
185         displaySize.width = display_width;
186     }
187     else // No Anamorphic
188     {
189         [fInfoField setStringValue: [NSString stringWithFormat:
190                                      NSLocalizedStringFromTable(@"Source: %dx%d, Output: %dx%d", @"Picture", @""), fTitle->width, fTitle->height,
191                                      fTitle->job->width, fTitle->job->height]];
192     }
193
194     NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
195     if( [self viewNeedsToResizeToSize:viewSize] )
196     {
197         /* In the case of loose anamorphic, do not resize the window when scaling down */
198         if (fTitle->job->pixel_ratio != 2 || [fWidthField intValue] == fTitle->width)
199         {
200             [self resizeSheetForViewSize:viewSize];
201             [self setViewSize:viewSize];
202         }
203     }
204
205     // Show the scaled text (use the height to check since the width can vary
206     // with anamorphic video).
207     if( ( ( int )viewSize.height ) != fTitle->height )
208     {
209         CGFloat scale = viewSize.width / ( ( CGFloat ) fTitle->width );
210         NSString *scaleString = [NSString stringWithFormat:
211                                  NSLocalizedString( @" (Preview scaled to %.0f%% actual size)",
212                                                    @"String shown when a preview is scaled" ),
213                                  scale * 100.0];
214         [fInfoField setStringValue: [[fInfoField stringValue] stringByAppendingString:scaleString]];
215     }
216
217     [fPrevButton setEnabled: ( fPicture > 0 )];
218     [fNextButton setEnabled: ( fPicture < 9 )];
219 }
220
221 - (IBAction) deblockSliderChanged: (id) sender
222 {
223     if ([fDeblockSlider floatValue] == 4.0)
224     {
225     [fDeblockField setStringValue: [NSString stringWithFormat: NSLocalizedStringFromTable(@"Off", @"Picture", @"")]];
226     }
227     else
228     {
229     [fDeblockField setStringValue: [NSString stringWithFormat: @"%.0f", [fDeblockSlider floatValue]]];
230     }
231         [self SettingsChanged: sender];
232 }
233
234 - (IBAction) SettingsChanged: (id) sender
235 {
236     hb_job_t * job = fTitle->job;
237     
238     autoCrop = ( [fCropMatrix selectedRow] == 0 );
239     [fCropTopStepper    setEnabled: !autoCrop];
240     [fCropBottomStepper setEnabled: !autoCrop];
241     [fCropLeftStepper   setEnabled: !autoCrop];
242     [fCropRightStepper  setEnabled: !autoCrop];
243
244     if( autoCrop )
245     {
246         memcpy( job->crop, fTitle->crop, 4 * sizeof( int ) );
247     }
248     else
249     {
250         job->crop[0] = [fCropTopStepper    intValue];
251         job->crop[1] = [fCropBottomStepper intValue];
252         job->crop[2] = [fCropLeftStepper   intValue];
253         job->crop[3] = [fCropRightStepper  intValue];
254     }
255     
256         if( [fAnamorphicPopUp indexOfSelectedItem] > 0 )
257         {
258         if ([fAnamorphicPopUp indexOfSelectedItem] == 2) // Loose anamorphic
259         {
260             job->pixel_ratio = 2;
261             [fWidthStepper setEnabled: YES];
262             [fWidthField setEnabled: YES];
263             /* We set job->width and call hb_set_anamorphic_size in libhb to do a "dry run" to get
264              * the values to be used by libhb for loose anamorphic
265              */
266             /* if the sender is the anamorphic popup, then we know that loose anamorphic has just
267              * been turned on, so snap the width to full width for the source.
268              */
269             if (sender == fAnamorphicPopUp)
270             {
271                 [fWidthStepper      setIntValue: fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3]];
272                 [fWidthField        setIntValue: fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3]];
273             }
274             job->width       = [fWidthStepper  intValue];
275             hb_set_anamorphic_size(job, &output_width, &output_height, &output_par_width, &output_par_height);
276             [fHeightStepper      setIntValue: output_height];
277             [fHeightField        setIntValue: output_height];
278             job->height      = [fHeightStepper intValue];
279             
280         }
281         else // must be "1" or strict anamorphic
282         {
283             [fWidthStepper      setIntValue: fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3]];
284             [fWidthField        setIntValue: fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3]];
285             
286             /* This will show correct anamorphic height values, but
287              show distorted preview picture ratio */
288             [fHeightStepper      setIntValue: fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1]];
289             [fHeightField        setIntValue: fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1]];
290             job->width       = [fWidthStepper  intValue];
291             job->height      = [fHeightStepper intValue];
292             
293             job->pixel_ratio = 1;
294             [fWidthStepper setEnabled: NO];
295             [fWidthField setEnabled: NO];
296         }
297         
298         /* if the sender is the Anamorphic checkbox, record the state
299          of KeepAspect Ratio so it can be reset if Anamorphic is unchecked again */
300         if (sender == fAnamorphicPopUp)
301         {
302             keepAspectRatioPreviousState = [fRatioCheck state];
303         }
304         [fRatioCheck setState:NSOffState];
305         [fRatioCheck setEnabled: NO];
306         
307         
308         [fHeightStepper setEnabled: NO];
309         [fHeightField setEnabled: NO];
310         
311     }
312     else
313         {
314         job->width       = [fWidthStepper  intValue];
315         job->height      = [fHeightStepper intValue];
316         job->pixel_ratio = 0;
317         [fWidthStepper setEnabled: YES];
318         [fWidthField setEnabled: YES];
319         [fHeightStepper setEnabled: YES];
320         [fHeightField setEnabled: YES];
321         [fRatioCheck setEnabled: YES];
322         /* if the sender is the Anamorphic checkbox, we return the
323          keep AR checkbox to its previous state */
324         if (sender == fAnamorphicPopUp)
325         {
326             [fRatioCheck setState:keepAspectRatioPreviousState];
327         }
328         
329         }
330         
331     job->keep_ratio  = ( [fRatioCheck state] == NSOnState );
332     
333         fPictureFilterSettings.deinterlace = [fDeinterlacePopUp indexOfSelectedItem];
334     /* if the gui deinterlace settings are fast through slowest, the job->deinterlace
335      value needs to be set to one, for the job as well as the previews showing deinterlacing
336      otherwise set job->deinterlace to 0 or "off" */
337     if (fPictureFilterSettings.deinterlace > 0)
338     {
339         job->deinterlace  = 1;
340     }
341     else
342     {
343         job->deinterlace  = 0;
344     }
345     fPictureFilterSettings.denoise     = [fDenoisePopUp indexOfSelectedItem];
346     
347     fPictureFilterSettings.detelecine  = [fDetelecineCheck state];
348     
349     if ([fDeblockField stringValue] == NSLocalizedStringFromTable(@"Off", @"Picture", @""))
350     {
351     fPictureFilterSettings.deblock  = 0;
352     }
353     else
354     {
355     fPictureFilterSettings.deblock  = [fDeblockField intValue];
356     }
357     
358     fPictureFilterSettings.decomb = [fDecombPopUp indexOfSelectedItem];
359
360     if( job->keep_ratio )
361     {
362         if( sender == fWidthStepper || sender == fRatioCheck ||
363            sender == fCropTopStepper || sender == fCropBottomStepper )
364         {
365             hb_fix_aspect( job, HB_KEEP_WIDTH );
366             if( job->height > fTitle->height )
367             {
368                 job->height = fTitle->height;
369                 hb_fix_aspect( job, HB_KEEP_HEIGHT );
370             }
371         }
372         else
373         {
374             hb_fix_aspect( job, HB_KEEP_HEIGHT );
375             if( job->width > fTitle->width )
376             {
377                 job->width = fTitle->width;
378                 hb_fix_aspect( job, HB_KEEP_WIDTH );
379             }
380         }
381         // hb_get_preview can't handle sizes that are larger than the original title
382         // dimensions
383         if( job->width > fTitle->width )
384             job->width = fTitle->width;
385
386         if( job->height > fTitle->height )
387             job->height = fTitle->height;
388     }
389
390     [fWidthStepper      setIntValue: job->width];
391     [fWidthField        setIntValue: job->width];
392     if( [fAnamorphicPopUp indexOfSelectedItem] < 2 )
393         {
394         [fHeightStepper     setIntValue: job->height];
395         [fHeightField       setIntValue: job->height];
396     }
397     [fCropTopStepper    setIntValue: job->crop[0]];
398     [fCropTopField      setIntValue: job->crop[0]];
399     [fCropBottomStepper setIntValue: job->crop[1]];
400     [fCropBottomField   setIntValue: job->crop[1]];
401     [fCropLeftStepper   setIntValue: job->crop[2]];
402     [fCropLeftField     setIntValue: job->crop[2]];
403     [fCropRightStepper  setIntValue: job->crop[3]];
404     [fCropRightField    setIntValue: job->crop[3]];
405     /* Sanity Check Here for < 16 px preview to avoid
406      crashing hb_get_preview. In fact, just for kicks
407      lets getting previews at a min limit of 32, since
408      no human can see any meaningful detail below that */
409     if (job->width >= 64 && job->height >= 64)
410     {
411         // Purge the existing picture previews so they get recreated the next time
412         // they are needed.
413         [self purgeImageCache];
414         [self displayPreview];
415     }
416 }
417
418 - (IBAction) PreviousPicture: (id) sender
419 {   
420     if( fPicture <= 0 )
421     {
422         return;
423     }
424     fPicture--;
425     [self displayPreview];
426 }
427
428 - (IBAction) NextPicture: (id) sender
429 {
430     if( fPicture >= 9 )
431     {
432         return;
433     }
434     fPicture++;
435     [self displayPreview];
436 }
437
438 - (IBAction) ClosePanel: (id) sender
439 {
440     if ([delegate respondsToSelector:@selector(pictureSettingsDidChange)])
441         [delegate pictureSettingsDidChange];
442
443     [NSApp endSheet:[self window]];
444     [[self window] orderOut:self];
445 }
446
447 - (BOOL) autoCrop
448 {
449     return autoCrop;
450 }
451 - (void) setAutoCrop: (BOOL) setting
452 {
453     autoCrop = setting;
454 }
455
456 - (BOOL) allowLooseAnamorphic
457 {
458     return allowLooseAnamorphic;
459 }
460
461 - (void) setAllowLooseAnamorphic: (BOOL) setting
462 {
463     allowLooseAnamorphic = setting;
464 }
465
466 - (int) detelecine
467 {
468     return fPictureFilterSettings.detelecine;
469 }
470
471 - (void) setDetelecine: (int) setting
472 {
473     fPictureFilterSettings.detelecine = setting;
474 }
475
476 - (int) deinterlace
477 {
478     return fPictureFilterSettings.deinterlace;
479 }
480
481 - (void) setDeinterlace: (int) setting {
482     fPictureFilterSettings.deinterlace = setting;
483 }
484 - (int) decomb
485 {
486     return fPictureFilterSettings.decomb;
487 }
488
489 - (void) setDecomb: (int) setting {
490     fPictureFilterSettings.decomb = setting;
491 }
492 - (int) denoise
493 {
494     return fPictureFilterSettings.denoise;
495 }
496
497 - (void) setDenoise: (int) setting
498 {
499     fPictureFilterSettings.denoise = setting;
500 }
501
502 - (int) deblock
503 {
504     return fPictureFilterSettings.deblock;
505 }
506
507 - (void) setDeblock: (int) setting
508 {
509     fPictureFilterSettings.deblock = setting;
510 }
511
512 - (void)showPanelInWindow: (NSWindow *)fWindow forTitle: (hb_title_t *)title
513 {
514     [self SetTitle:title];
515
516     [NSApp beginSheet:[self window]
517        modalForWindow:fWindow
518         modalDelegate:nil
519        didEndSelector:nil
520           contextInfo:NULL];
521 }
522
523
524 // This function converts an image created by libhb (specified via pictureIndex) into
525 // an NSImage suitable for the GUI code to use. If removeBorders is YES,
526 // makeImageForPicture crops the image generated by libhb stripping off the gray
527 // border around the content. This is the low-level method that generates the image.
528 // -imageForPicture calls this function whenever it can't find an image in its cache.
529 + (NSImage *) makeImageForPicture: (int)pictureIndex
530                 libhb:(hb_handle_t*)handle
531                 title:(hb_title_t*)title
532                 removeBorders:(BOOL)removeBorders
533 {
534     if (removeBorders)
535     {
536         //     |<---------- title->width ----------->|
537         //     |   |<---- title->job->width ---->|   |
538         //     |   |                             |   |
539         //     .......................................
540         //     ....+-----------------------------+....
541         //     ....|                             |....<-- gray border
542         //     ....|                             |....
543         //     ....|                             |....
544         //     ....|                             |<------- image
545         //     ....|                             |....
546         //     ....|                             |....
547         //     ....|                             |....
548         //     ....|                             |....
549         //     ....|                             |....
550         //     ....+-----------------------------+....
551         //     .......................................
552
553         static uint8_t * buffer;
554         static int bufferSize;
555
556         // Make sure we have a big enough buffer to receive the image from libhb. libhb
557         // creates images with a one-pixel border around the original content. Hence we
558         // add 2 pixels horizontally and vertically to the buffer size.
559         int srcWidth = title->width + 2;
560         int srcHeight= title->height + 2;
561         int newSize;
562         newSize = srcWidth * srcHeight * 4;
563         if( bufferSize < newSize )
564         {
565             bufferSize = newSize;
566             buffer     = (uint8_t *) realloc( buffer, bufferSize );
567         }
568
569         hb_get_preview( handle, title, pictureIndex, buffer );
570
571         // Create an NSBitmapImageRep and copy the libhb image into it, converting it from
572         // libhb's format to one suitable for NSImage. Along the way, we'll strip off the
573         // border around libhb's image.
574         
575         // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
576         // Alpha is ignored.
577         
578         int dstWidth = title->job->width;
579         int dstHeight = title->job->height;
580         NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat;
581         NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc]
582                 initWithBitmapDataPlanes:nil
583                 pixelsWide:dstWidth
584                 pixelsHigh:dstHeight
585                 bitsPerSample:8
586                 samplesPerPixel:3   // ignore alpha
587                 hasAlpha:NO
588                 isPlanar:NO
589                 colorSpaceName:NSCalibratedRGBColorSpace
590                 bitmapFormat:bitmapFormat
591                 bytesPerRow:dstWidth * 4
592                 bitsPerPixel:32] autorelease];
593
594         int borderTop = (srcHeight - dstHeight) / 2;
595         int borderLeft = (srcWidth - dstWidth) / 2;
596         
597         UInt32 * src = (UInt32 *)buffer;
598         UInt32 * dst = (UInt32 *)[imgrep bitmapData];
599         src += borderTop * srcWidth;    // skip top rows in src to get to first row of dst
600         src += borderLeft;              // skip left pixels in src to get to first pixel of dst
601         for (int r = 0; r < dstHeight; r++)
602         {
603             for (int c = 0; c < dstWidth; c++)
604 #if TARGET_RT_LITTLE_ENDIAN
605                 *dst++ = Endian32_Swap(*src++);
606 #else
607                 *dst++ = *src++;
608 #endif
609             src += (srcWidth - dstWidth);   // skip to next row in src
610         }
611
612         NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(dstWidth, dstHeight)] autorelease];
613         [img addRepresentation:imgrep];
614
615         return img;
616     }
617     else
618     {
619         // Make sure we have big enough buffer
620         static uint8_t * buffer;
621         static int bufferSize;
622
623         int newSize;
624         newSize = ( title->width + 2 ) * (title->height + 2 ) * 4;
625         if( bufferSize < newSize )
626         {
627             bufferSize = newSize;
628             buffer     = (uint8_t *) realloc( buffer, bufferSize );
629         }
630
631         hb_get_preview( handle, title, pictureIndex, buffer );
632
633         // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
634         // We'll copy that into an NSImage swapping it to ARGB in the process. Alpha is
635         // ignored.
636         int width = title->width + 2;      // hblib adds a one-pixel border to the image
637         int height = title->height + 2;
638         int numPixels = width * height;
639         NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat;
640         NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc]
641                 initWithBitmapDataPlanes:nil
642                 pixelsWide:width
643                 pixelsHigh:height
644                 bitsPerSample:8
645                 samplesPerPixel:3   // ignore alpha
646                 hasAlpha:NO
647                 isPlanar:NO
648                 colorSpaceName:NSCalibratedRGBColorSpace
649                 bitmapFormat:bitmapFormat
650                 bytesPerRow:width * 4
651                 bitsPerPixel:32] autorelease];
652
653         UInt32 * src = (UInt32 *)buffer;
654         UInt32 * dst = (UInt32 *)[imgrep bitmapData];
655         for (int i = 0; i < numPixels; i++)
656 #if TARGET_RT_LITTLE_ENDIAN
657             *dst++ = Endian32_Swap(*src++);
658 #else
659             *dst++ = *src++;
660 #endif
661
662         NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(width, height)] autorelease];
663         [img addRepresentation:imgrep];
664
665         return img;
666     }
667 }
668
669 // Returns the preview image for the specified index, retrieving it from its internal
670 // cache or by calling makeImageForPicture if it is not cached. Generally, you should
671 // use imageForPicture so that images are cached. Calling makeImageForPicture will
672 // always generate a new copy of the image.
673 - (NSImage *) imageForPicture: (int) pictureIndex
674 {
675     // The preview for the specified index may not currently exist, so this method
676     // generates it if necessary.
677     NSString * key = [NSString stringWithFormat:@"%d", pictureIndex];
678     NSImage * theImage = [fPicturePreviews objectForKey:key];
679     if (!theImage)
680     {
681         theImage = [PictureController makeImageForPicture:pictureIndex libhb:fHandle title:fTitle removeBorders: NO];
682         [fPicturePreviews setObject:theImage forKey:key];
683     }
684     return theImage;
685 }
686
687 // Purges all images from the cache. The next call to imageForPicture will cause a new
688 // image to be generated.
689 - (void) purgeImageCache
690 {
691     [fPicturePreviews removeAllObjects];
692 }
693
694 @end
695
696 @implementation PictureController (Private)
697
698 //
699 // -[PictureController(Private) optimalViewSizeForImageSize:]
700 //
701 // Given the size of the preview image to be shown, returns the best possible
702 // size for the view.
703 //
704 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize
705 {
706     // The min size is 320x240
707     CGFloat minWidth = 320.0;
708     CGFloat minHeight = 240.0;
709
710     // The max size of the view is when the sheet is taking up 85% of the screen.
711     NSSize screenSize = [[NSScreen mainScreen] frame].size;
712     NSSize sheetSize = [[self window] frame].size;
713     NSSize viewAreaSize = [fPictureViewArea frame].size;
714     CGFloat paddingX = sheetSize.width - viewAreaSize.width;
715     CGFloat paddingY = sheetSize.height - viewAreaSize.height;
716     CGFloat maxWidth = (0.85 * screenSize.width) - paddingX;
717     CGFloat maxHeight = (0.85 * screenSize.height) - paddingY;
718     
719     NSSize resultSize = imageSize;
720     
721     // Its better to have a view that's too small than a view that's too big, so
722     // apply the maximum constraints last.
723     if( resultSize.width < minWidth )
724     {
725         resultSize.height *= (minWidth / resultSize.width);
726         resultSize.width = minWidth;
727     }
728     if( resultSize.height < minHeight )
729     {
730         resultSize.width *= (minHeight / resultSize.height);
731         resultSize.height = minHeight;
732     }
733     if( resultSize.width > maxWidth )
734     {
735         resultSize.height *= (maxWidth / resultSize.width);
736         resultSize.width = maxWidth;
737     }
738     if( resultSize.height > maxHeight )
739     {
740         resultSize.width *= (maxHeight / resultSize.height);
741         resultSize.height = maxHeight;
742     }
743     
744     return resultSize;
745 }
746
747 //
748 // -[PictureController(Private) resizePanelForViewSize:animate:]
749 //
750 // Resizes the entire sheet to accomodate a view of a particular size.
751 //
752 - (void)resizeSheetForViewSize: (NSSize)viewSize
753 {
754     // Figure out the deltas for the new frame area
755     NSSize currentSize = [fPictureViewArea frame].size;
756     CGFloat deltaX = viewSize.width - currentSize.width;
757     CGFloat deltaY = viewSize.height - currentSize.height;
758
759     // Now resize the whole panel by those same deltas, but don't exceed the min
760     NSRect frame = [[self window] frame];
761     NSSize maxSize = [[self window] maxSize];
762     NSSize minSize = [[self window] minSize];
763     frame.size.width += deltaX;
764     frame.size.height += deltaY;
765     if( frame.size.width < minSize.width )
766     {
767         frame.size.width = minSize.width;
768     }
769     if( frame.size.height < minSize.height )
770     {
771         frame.size.height = minSize.height;
772     }
773
774     // But now the sheet is off-center, so also shift the origin to center it and
775     // keep the top aligned.
776     if( frame.size.width != [[self window] frame].size.width )
777         frame.origin.x -= (deltaX / 2.0);
778
779     if( frame.size.height != [[self window] frame].size.height )
780         frame.origin.y -= deltaY;
781
782     [[self window] setFrame:frame display:YES animate:YES];
783 }
784
785 //
786 // -[PictureController(Private) setViewSize:]
787 //
788 // Changes the view's size and centers it vertically inside of its area.
789 // Assumes resizeSheetForViewSize: has already been called.
790 //
791 - (void)setViewSize: (NSSize)viewSize
792 {
793     [fPictureView setFrameSize:viewSize];
794     
795     // center it vertically
796     NSPoint origin = [fPictureViewArea frame].origin;
797     origin.y += ([fPictureViewArea frame].size.height -
798                  [fPictureView frame].size.height) / 2.0;
799     [fPictureView setFrameOrigin:origin];
800 }
801
802 //
803 // -[PictureController(Private) viewNeedsToResizeToSize:]
804 //
805 // Returns YES if the view will need to resize to match the given size.
806 //
807 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize
808 {
809     NSSize viewSize = [fPictureView frame].size;
810     return (newSize.width != viewSize.width || newSize.height != viewSize.height);
811 }
812
813 @end