OSDN Git Service

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