OSDN Git Service

Repeat after me, eddyg is a wally.
[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.m0k.org/>.
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 static int GetAlignedSize( int size )
19 {
20     int result = 1;
21     while( result < size )
22     {
23         result *= 2;
24     }
25     return result;
26 }
27
28 @implementation PictureController
29
30 - (id)initWithDelegate:(id)del
31 {
32         if (self = [super init])
33         {
34                 delegate = del;
35         [self loadMyNibFile];
36         }
37         return self;
38 }
39
40 - (void) SetHandle: (hb_handle_t *) handle
41 {
42     fHandle = handle;
43
44     fHasQE = CGDisplayUsesOpenGLAcceleration( kCGDirectMainDisplay );
45
46     fBuffer     = NULL;
47     fBufferSize = 0;
48     fTexBuf[0]  = NULL;
49     fTexBuf[1]  = NULL;
50     fTexBufSize = 0;
51
52     [fWidthStepper  setValueWraps: NO];
53     [fWidthStepper  setIncrement: 16];
54     [fWidthStepper  setMinValue: 64];
55     [fHeightStepper setValueWraps: NO];
56     [fHeightStepper setIncrement: 16];
57     [fHeightStepper setMinValue: 64];
58
59     [fCropTopStepper    setIncrement: 2];
60     [fCropTopStepper    setMinValue:  0];
61     [fCropBottomStepper setIncrement: 2];
62     [fCropBottomStepper setMinValue:  0];
63     [fCropLeftStepper   setIncrement: 2];
64     [fCropLeftStepper   setMinValue:  0];
65     [fCropRightStepper  setIncrement: 2];
66     [fCropRightStepper  setMinValue:  0];
67 }
68
69 - (void) SetTitle: (hb_title_t *) title
70 {
71     hb_job_t * job = title->job;
72
73     fTitle = title;
74
75     /* Make sure we have big enough buffers */
76     int newSize;
77     newSize = ( title->width + 2 ) * (title->height + 2 ) * 4;
78     if( fBufferSize < newSize )
79     {
80         fBufferSize = newSize;
81         fBuffer     = (uint8_t *) realloc( fBuffer, fBufferSize );
82     }
83     if( !fHasQE )
84     {
85         newSize = ( GetAlignedSize( title->width + 2 ) *
86             GetAlignedSize( title->height + 2 ) * 4 );
87     }
88     if( fTexBufSize < newSize )
89     {
90         fTexBufSize = newSize;
91         fTexBuf[0]  = (uint8_t *) realloc( fTexBuf[0], fTexBufSize );
92         fTexBuf[1]  = (uint8_t *) realloc( fTexBuf[1], fTexBufSize );
93     }
94
95
96     [fWidthStepper      setMaxValue: title->width];
97     [fWidthStepper      setIntValue: job->width];
98     [fWidthField        setIntValue: job->width];
99     [fHeightStepper     setMaxValue: title->height];
100     [fHeightStepper     setIntValue: job->height];
101     [fHeightField       setIntValue: job->height];
102     [fRatioCheck        setState:    job->keep_ratio ? NSOnState : NSOffState];
103     [fCropTopStepper    setMaxValue: title->height/2-2];
104     [fCropBottomStepper setMaxValue: title->height/2-2];
105     [fCropLeftStepper   setMaxValue: title->width/2-2];
106     [fCropRightStepper  setMaxValue: title->width/2-2];
107     
108         
109         /* we use a popup to show the deinterlace settings */
110         [fDeinterlacePopUp removeAllItems];
111     [fDeinterlacePopUp addItemWithTitle: @"None"];
112     [fDeinterlacePopUp addItemWithTitle: @"Fast"];
113     [fDeinterlacePopUp addItemWithTitle: @"Slow"];
114         [fDeinterlacePopUp addItemWithTitle: @"Slower"];
115         [fDeinterlacePopUp addItemWithTitle: @"Slowest"];
116     
117         /* Set deinterlaces level according to the integer in the main window */
118         [fDeinterlacePopUp selectItemAtIndex: fPictureFilterSettings.deinterlace];
119
120         [fPARCheck setState:(job->pixel_ratio ? NSOnState : NSOffState)];
121     /* We initially set the previous state of keep ar to on */
122     keepAspectRatioPreviousState = 1;
123         if (!autoCrop)
124         {
125         [fCropMatrix  selectCellAtRow: 1 column:0];
126         /* If auto, lets set the crop steppers according to current job->crop values */
127         [fCropTopStepper    setIntValue: job->crop[0]];
128         [fCropTopField      setIntValue: job->crop[0]];
129         [fCropBottomStepper setIntValue: job->crop[1]];
130         [fCropBottomField   setIntValue: job->crop[1]];
131         [fCropLeftStepper   setIntValue: job->crop[2]];
132         [fCropLeftField     setIntValue: job->crop[2]];
133         [fCropRightStepper  setIntValue: job->crop[3]];
134         [fCropRightField    setIntValue: job->crop[3]];
135         }
136         else
137         {
138         [fCropMatrix  selectCellAtRow: 0 column:0];
139         }
140         
141         
142         
143         /* we use a popup to show the denoise settings */
144         [fDenoisePopUp removeAllItems];
145     [fDenoisePopUp addItemWithTitle: @"None"];
146     [fDenoisePopUp addItemWithTitle: @"Weak"];
147         [fDenoisePopUp addItemWithTitle: @"Medium"];
148     [fDenoisePopUp addItemWithTitle: @"Strong"];
149         /* Set denoises level according to the integer in the main window */
150         [fDenoisePopUp selectItemAtIndex: fPictureFilterSettings.denoise];
151         
152     MaxOutputWidth = job->width;
153         MaxOutputHeight = job->height;
154     fPicture = 0;
155
156     [self SettingsChanged: nil];
157 }
158
159 - (void) Display: (int) anim
160 {
161     hb_get_preview( fHandle, fTitle, fPicture, fBuffer );
162
163     /* Backup previous picture (for effects) */
164     memcpy( fTexBuf[1], fTexBuf[0], fTexBufSize );
165
166     if( fHasQE )
167     {
168         /* Simply copy */
169         memcpy( fTexBuf[0], fBuffer, fTexBufSize );
170     }
171     else
172     {
173         /* Copy line by line */
174         uint8_t * in  = fBuffer;
175         uint8_t * out = fTexBuf[0];
176                 
177         for( int i = fTitle->height + 2; i--; )
178         {
179             memcpy( out, in, 4 * ( fTitle->width + 2 ) );
180             in  += 4 * ( fTitle->width + 2 );
181             out += 4 * GetAlignedSize( fTitle->width + 2 );
182         }
183         
184     }
185
186     if( [fEffectsCheck state] == NSOffState )
187     {
188         anim = HB_ANIMATE_NONE;
189     }
190     else if( [[NSApp currentEvent] modifierFlags] & NSShiftKeyMask )
191     {
192         anim |= HB_ANIMATE_SLOW;
193     }
194
195     [fPictureGLView Display: anim buffer1: fTexBuf[0]
196         buffer2: fTexBuf[1] width: ( fTitle->width + 2 )
197         height: ( fTitle->height + 2 )];
198         
199         /* Set the Output Display below the Preview Picture*/
200         int titlewidth = fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3];
201         int arpwidth = fTitle->job->pixel_aspect_width;
202         int arpheight = fTitle->job->pixel_aspect_height;
203         int displayparwidth = titlewidth * arpwidth / arpheight;
204         int displayparheight = fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1];
205
206     NSSize displaySize = NSMakeSize( (float)fTitle->width, (float)fTitle->height );
207     if( fTitle->job->pixel_ratio == 1 )
208     {
209         [fInfoField setStringValue:[NSString stringWithFormat:
210                             @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d",
211                             fTitle->width, fTitle->height, titlewidth,
212                             displayparheight, displayparwidth, displayparheight]];
213         displaySize.width *= ((float)arpwidth) / ((float)arpheight);
214     }
215     else
216     {
217     [fInfoField setStringValue: [NSString stringWithFormat:
218         @"Source: %dx%d, Output: %dx%d", fTitle->width, fTitle->height,
219         fTitle->job->width, fTitle->job->height]];
220     }
221
222     NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
223     if( [self viewNeedsToResizeToSize:viewSize] )
224     {
225         [self resizeSheetForViewSize:viewSize];
226         [self setViewSize:viewSize];
227     }
228     
229     // Show the scaled text (use the height to check since the width can vary
230     // with anamorphic video).
231     if( ((int)viewSize.height) != fTitle->height )
232     {
233         float scale = viewSize.width / ((float)fTitle->width);
234         NSString *scaleString = [NSString stringWithFormat:
235             NSLocalizedString( @" (Preview scaled to %.0f%% actual size)",
236                                @"String shown when a preview is scaled" ),
237             scale * 100.0];
238         [fInfoField setStringValue:
239             [[fInfoField stringValue] stringByAppendingString:scaleString]];
240     }
241
242     [fPrevButton setEnabled: ( fPicture > 0 )];
243     [fNextButton setEnabled: ( fPicture < 9 )];
244 }
245
246 - (IBAction) SettingsChanged: (id) sender
247 {
248     hb_job_t * job = fTitle->job;
249     
250         if( [fPARCheck state] == NSOnState )
251         {
252         [fWidthStepper      setIntValue: MaxOutputWidth];
253         [fWidthField        setIntValue: MaxOutputWidth];
254         
255         /* This will show correct anamorphic height values, but
256             show distorted preview picture ratio */
257         [fHeightStepper      setIntValue: fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1]];
258         [fHeightField        setIntValue: fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1]];
259
260         /* if the sender is the Anamorphic checkbox, record the state
261            of KeepAspect Ratio so it can be reset if Anamorphic is unchecked again */
262         if (sender == fPARCheck)
263         {
264         keepAspectRatioPreviousState = [fRatioCheck state];
265         }
266         [fRatioCheck setState:NSOffState];
267         [fRatioCheck setEnabled: NO];
268         
269         [fWidthStepper setEnabled: NO];
270         [fWidthField setEnabled: NO];
271         [fHeightStepper setEnabled: NO];
272         [fHeightField setEnabled: NO];
273         
274     }
275     else
276         {
277         [fWidthStepper setEnabled: YES];
278         [fWidthField setEnabled: YES];
279         [fHeightStepper setEnabled: YES];
280         [fHeightField setEnabled: YES];
281         [fRatioCheck setEnabled: YES];
282         /* if the sender is the Anamorphic checkbox, we return the
283            keep AR checkbox to its previous state */
284         if (sender == fPARCheck)
285         {
286         [fRatioCheck setState:keepAspectRatioPreviousState];
287         }
288         
289         }
290         
291     job->width       = [fWidthStepper  intValue];
292     job->height      = [fHeightStepper intValue];
293     job->keep_ratio  = ( [fRatioCheck state] == NSOnState );
294     
295         fPictureFilterSettings.deinterlace = [fDeinterlacePopUp indexOfSelectedItem];
296     /* if the gui deinterlace settings are fast through slowest, the job->deinterlace
297        value needs to be set to one, for the job as well as the previews showing deinterlacing
298        otherwise set job->deinterlace to 0 or "off" */
299     if (fPictureFilterSettings.deinterlace > 0)
300     {
301     job->deinterlace  = 1;
302     }
303     else
304     {
305     job->deinterlace  = 0;
306     }
307     fPictureFilterSettings.denoise     = [fDenoisePopUp indexOfSelectedItem];
308     fPictureFilterSettings.detelecine  = [fDetelecineCheck state];
309         job->pixel_ratio = ( [fPARCheck state] == NSOnState );
310
311     autoCrop = ( [fCropMatrix selectedRow] == 0 );
312     [fCropTopStepper    setEnabled: !autoCrop];
313     [fCropBottomStepper setEnabled: !autoCrop];
314     [fCropLeftStepper   setEnabled: !autoCrop];
315     [fCropRightStepper  setEnabled: !autoCrop];
316
317     if( autoCrop )
318     {
319         memcpy( job->crop, fTitle->crop, 4 * sizeof( int ) );
320     }
321     else
322     {
323         job->crop[0] = [fCropTopStepper    intValue];
324         job->crop[1] = [fCropBottomStepper intValue];
325         job->crop[2] = [fCropLeftStepper   intValue];
326         job->crop[3] = [fCropRightStepper  intValue];
327     }
328
329     if( job->keep_ratio )
330     {
331         if( sender == fWidthStepper || sender == fRatioCheck ||
332             sender == fCropTopStepper || sender == fCropBottomStepper )
333         {
334             hb_fix_aspect( job, HB_KEEP_WIDTH );
335             if( job->height > fTitle->height )
336             {
337                 job->height = fTitle->height;
338                 hb_fix_aspect( job, HB_KEEP_HEIGHT );
339             }
340         }
341         else
342         {
343             hb_fix_aspect( job, HB_KEEP_HEIGHT );
344             if( job->width > fTitle->width )
345             {
346                 job->width = fTitle->width;
347                 hb_fix_aspect( job, HB_KEEP_WIDTH );
348             }
349         }
350     }
351     
352     [fWidthStepper      setIntValue: job->width];
353     [fWidthField        setIntValue: job->width];
354     [fHeightStepper     setIntValue: job->height];
355     [fHeightField       setIntValue: job->height];
356     [fCropTopStepper    setIntValue: job->crop[0]];
357     [fCropTopField      setIntValue: job->crop[0]];
358     [fCropBottomStepper setIntValue: job->crop[1]];
359     [fCropBottomField   setIntValue: job->crop[1]];
360     [fCropLeftStepper   setIntValue: job->crop[2]];
361     [fCropLeftField     setIntValue: job->crop[2]];
362     [fCropRightStepper  setIntValue: job->crop[3]];
363     [fCropRightField    setIntValue: job->crop[3]];
364     /* Sanity Check Here for < 16 px preview to avoid
365        crashing hb_get_preview. In fact, just for kicks
366        lets getting previews at a min limit of 32, since
367        no human can see any meaningful detail below that */
368     if (job->width >= 64 && job->height >= 64)
369     {
370         [self Display: HB_ANIMATE_NONE];
371     }
372 }
373
374 - (IBAction) PreviousPicture: (id) sender
375 {   
376     if( fPicture <= 0 )
377     {
378         return;
379     }
380     fPicture--;
381     [self Display: HB_ANIMATE_BACKWARD];
382 }
383
384 - (IBAction) NextPicture: (id) sender
385 {
386     if( fPicture >= 9 )
387     {
388         return;
389     }
390     fPicture++;
391     [self Display: HB_ANIMATE_FORWARD];
392 }
393
394 - (IBAction) ClosePanel: (id) sender
395 {
396     if ([delegate respondsToSelector:@selector(pictureSettingsDidChange)])
397         [delegate pictureSettingsDidChange];
398         
399     [NSApp endSheet: fPicturePanel];
400     [fPicturePanel orderOut: self];
401 }
402
403 - (BOOL) autoCrop
404 {
405     return autoCrop;
406 }
407 - (void) setAutoCrop: (BOOL) setting
408 {
409     autoCrop = setting;
410 }
411
412 - (int) detelecine
413 {
414     return fPictureFilterSettings.detelecine;
415 }
416
417 - (void) setDetelecine: (int) setting
418 {
419     fPictureFilterSettings.detelecine = setting;
420 }
421
422 - (int) deinterlace
423 {
424     return fPictureFilterSettings.deinterlace;
425 }
426
427 - (void) setDeinterlace: (int) setting {
428     fPictureFilterSettings.deinterlace = setting;
429 }
430
431 - (int) denoise
432 {
433     return fPictureFilterSettings.denoise;
434 }
435
436 - (void) setDenoise: (int) setting
437 {
438     fPictureFilterSettings.denoise = setting;
439 }
440
441 - (void)showPanelInWindow: (NSWindow *)fWindow forTitle: (hb_title_t *)title
442 {
443     [self SetTitle:title];
444     
445     [NSApp beginSheet:fPicturePanel
446        modalForWindow:fWindow
447         modalDelegate:nil
448        didEndSelector:nil
449           contextInfo:NULL];
450 }
451
452 - (BOOL) loadMyNibFile
453 {
454     if(![NSBundle loadNibNamed:@"PictureSettings" owner:self])
455     {
456         NSLog(@"Warning! Could not load myNib file.\n");
457         return NO;
458     }
459     
460     return YES;
461 }
462
463 @end
464
465 @implementation PictureController (Private)
466
467 //
468 // -[PictureController(Private) optimalViewSizeForImageSize:]
469 //
470 // Given the size of the preview image to be shown, returns the best possible
471 // size for the OpenGL view.
472 //
473 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize
474 {
475     // The min size is 320x240
476     float minWidth = 320.0;
477     float minHeight = 240.0;
478     
479     // The max size of the view is when the sheet is taking up 85% of the screen.
480     NSSize screenSize = [[NSScreen mainScreen] frame].size;
481     NSSize sheetSize = [fPicturePanel frame].size;
482     NSSize viewAreaSize = [fPictureGLViewArea frame].size;
483     float paddingX = sheetSize.width - viewAreaSize.width;
484     float paddingY = sheetSize.height - viewAreaSize.height;
485     float maxWidth = (0.85 * screenSize.width) - paddingX;
486     float maxHeight = (0.85 * screenSize.height) - paddingY;
487     
488     NSSize resultSize = imageSize;
489     
490     // Its better to have a view that's too small than a view that's too big, so
491     // apply the maximum constraints last.
492     if( resultSize.width < minWidth )
493     {
494         resultSize.height *= (minWidth / resultSize.width);
495         resultSize.width = minWidth;
496     }
497     if( resultSize.height < minHeight )
498     {
499         resultSize.width *= (minHeight / resultSize.height);
500         resultSize.height = minHeight;
501     }
502     if( resultSize.width > maxWidth )
503     {
504         resultSize.height *= (maxWidth / resultSize.width);
505         resultSize.width = maxWidth;
506     }
507     if( resultSize.height > maxHeight )
508     {
509         resultSize.width *= (maxHeight / resultSize.height);
510         resultSize.height = maxHeight;
511     }
512     
513     return resultSize;
514 }
515
516 //
517 // -[PictureController(Private) resizePanelForViewSize:animate:]
518 //
519 // Resizes the entire sheet to accomodate an OpenGL view of a particular size.
520 //
521 - (void)resizeSheetForViewSize: (NSSize)viewSize
522 {
523     // Figure out the deltas for the new frame area
524     NSSize currentSize = [fPictureGLViewArea frame].size;
525     float deltaX = viewSize.width - currentSize.width;
526     float deltaY = viewSize.height - currentSize.height;
527     
528     // Now resize the whole panel by those same deltas, but don't exceed the min
529     NSRect frame = [fPicturePanel frame];
530     NSSize maxSize = [fPicturePanel maxSize];
531     NSSize minSize = [fPicturePanel minSize];
532     frame.size.width += deltaX;
533     frame.size.height += deltaY;
534     if( frame.size.width < minSize.width )
535     {
536         frame.size.width = minSize.width;
537     }
538     if( frame.size.height < minSize.height )
539     {
540         frame.size.height = minSize.height;
541     }
542
543     // But now the sheet is off-center, so also shift the origin to center it and
544     // keep the top aligned.
545     frame.origin.x -= (deltaX / 2.0);
546     frame.origin.y -= deltaY;
547
548     [fPicturePanel setFrame:frame display:YES animate:YES];
549 }
550
551 //
552 // -[PictureController(Private) setViewSize:]
553 //
554 // Changes the OpenGL view's size and centers it vertially inside of its area.
555 // Assumes resizeSheetForViewSize: has already been called.
556 //
557 - (void)setViewSize: (NSSize)viewSize
558 {
559     [fPictureGLView setFrameSize:viewSize];
560     
561     // center it vertically
562     NSPoint origin = [fPictureGLViewArea frame].origin;
563     origin.y += ([fPictureGLViewArea frame].size.height -
564                  [fPictureGLView frame].size.height) / 2.0;
565     [fPictureGLView setFrameOrigin:origin];
566 }
567
568 //
569 // -[PictureController(Private) viewNeedsToResizeToSize:]
570 //
571 // Returns YES if the view will need to resize to match the given size.
572 //
573 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize
574 {
575     NSSize viewSize = [fPictureGLView frame].size;
576     return (newSize.width != viewSize.width || newSize.height != viewSize.height);
577 }
578
579
580 @end