1 /* $Id: PictureController.mm,v 1.11 2005/08/01 15:10:44 titer Exp $
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. */
7 #include "PictureController.h"
9 @interface PictureController (Private)
11 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize;
12 - (void)resizeSheetForViewSize: (NSSize)viewSize;
13 - (void)setViewSize: (NSSize)viewSize;
14 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize;
18 static int GetAlignedSize( int size )
21 while( result < size )
28 @implementation PictureController
30 - (id)initWithDelegate:(id)del
32 if (self = [super init])
40 - (void) SetHandle: (hb_handle_t *) handle
44 fHasQE = CGDisplayUsesOpenGLAcceleration( kCGDirectMainDisplay );
52 [fWidthStepper setValueWraps: NO];
53 [fWidthStepper setIncrement: 16];
54 [fWidthStepper setMinValue: 64];
55 [fHeightStepper setValueWraps: NO];
56 [fHeightStepper setIncrement: 16];
57 [fHeightStepper setMinValue: 64];
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];
69 - (void) SetTitle: (hb_title_t *) title
71 hb_job_t * job = title->job;
75 /* Make sure we have big enough buffers */
77 newSize = ( title->width + 2 ) * (title->height + 2 ) * 4;
78 if( fBufferSize < newSize )
80 fBufferSize = newSize;
81 fBuffer = (uint8_t *) realloc( fBuffer, fBufferSize );
85 newSize = ( GetAlignedSize( title->width + 2 ) *
86 GetAlignedSize( title->height + 2 ) * 4 );
88 if( fTexBufSize < newSize )
90 fTexBufSize = newSize;
91 fTexBuf[0] = (uint8_t *) realloc( fTexBuf[0], fTexBufSize );
92 fTexBuf[1] = (uint8_t *) realloc( fTexBuf[1], fTexBufSize );
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];
108 /* Populate the Anamorphic NSPopUp button here */
109 [fAnamorphicPopUp removeAllItems];
110 [fAnamorphicPopUp addItemWithTitle: @"None"];
111 [fAnamorphicPopUp addItemWithTitle: @"Strict"];
112 if (allowLooseAnamorphic)
114 [fAnamorphicPopUp addItemWithTitle: @"Loose"];
116 [fAnamorphicPopUp selectItemAtIndex: job->pixel_ratio];
118 /* Set deinterlaces level according to the integer in the main window */
119 [fDeinterlacePopUp selectItemAtIndex: fPictureFilterSettings.deinterlace];
121 /* We initially set the previous state of keep ar to on */
122 keepAspectRatioPreviousState = 1;
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]];
138 [fCropMatrix selectCellAtRow: 0 column:0];
142 MaxOutputWidth = title->width - job->crop[2] - job->crop[3];
143 MaxOutputHeight = title->height - job->crop[0] - job->crop[1];
144 [self SettingsChanged: nil];
147 /* we use this to setup the initial picture filters upon first launch, after that their states
148 are maintained across different sources */
149 - (void) setInitialPictureFilters
151 /* we use a popup to show the deinterlace settings */
152 [fDeinterlacePopUp removeAllItems];
153 [fDeinterlacePopUp addItemWithTitle: @"None"];
154 [fDeinterlacePopUp addItemWithTitle: @"Fast"];
155 [fDeinterlacePopUp addItemWithTitle: @"Slow"];
156 [fDeinterlacePopUp addItemWithTitle: @"Slower"];
157 [fDeinterlacePopUp addItemWithTitle: @"Slowest"];
159 /* Set deinterlaces level according to the integer in the main window */
160 [fDeinterlacePopUp selectItemAtIndex: fPictureFilterSettings.deinterlace];
162 /* we use a popup to show the denoise settings */
163 [fDenoisePopUp removeAllItems];
164 [fDenoisePopUp addItemWithTitle: @"None"];
165 [fDenoisePopUp addItemWithTitle: @"Weak"];
166 [fDenoisePopUp addItemWithTitle: @"Medium"];
167 [fDenoisePopUp addItemWithTitle: @"Strong"];
168 /* Set denoises level according to the integer in the main window */
169 [fDenoisePopUp selectItemAtIndex: fPictureFilterSettings.denoise];
172 - (void) Display: (int) anim
174 hb_get_preview( fHandle, fTitle, fPicture, fBuffer );
176 /* Backup previous picture (for effects) */
177 memcpy( fTexBuf[1], fTexBuf[0], fTexBufSize );
182 memcpy( fTexBuf[0], fBuffer, fTexBufSize );
186 /* Copy line by line */
187 uint8_t * in = fBuffer;
188 uint8_t * out = fTexBuf[0];
190 for( int i = fTitle->height + 2; i--; )
192 memcpy( out, in, 4 * ( fTitle->width + 2 ) );
193 in += 4 * ( fTitle->width + 2 );
194 out += 4 * GetAlignedSize( fTitle->width + 2 );
199 if( [fEffectsCheck state] == NSOffState )
201 anim = HB_ANIMATE_NONE;
203 else if( [[NSApp currentEvent] modifierFlags] & NSShiftKeyMask )
205 anim |= HB_ANIMATE_SLOW;
208 [fPictureGLView Display: anim buffer1: fTexBuf[0]
209 buffer2: fTexBuf[1] width: ( fTitle->width + 2 )
210 height: ( fTitle->height + 2 )];
212 NSSize displaySize = NSMakeSize( (float)fTitle->width, (float)fTitle->height );
213 /* Set the picture size display fields below the Preview Picture*/
214 if( fTitle->job->pixel_ratio == 1 ) // Original PAR Implementation
216 output_width = fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3];
217 output_height = fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1];
218 display_width = output_width * fTitle->job->pixel_aspect_width / fTitle->job->pixel_aspect_height;
219 [fInfoField setStringValue:[NSString stringWithFormat:
220 @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d",
221 fTitle->width, fTitle->height, output_width, output_height, display_width, output_height]];
222 displaySize.width *= ((float)fTitle->job->pixel_aspect_width) / ((float)fTitle->job->pixel_aspect_height);
224 else if (fTitle->job->pixel_ratio == 2) // Loose Anamorphic
226 display_width = output_width * output_par_width / output_par_height;
227 [fInfoField setStringValue:[NSString stringWithFormat:
228 @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d",
229 fTitle->width, fTitle->height, output_width, output_height, display_width, output_height]];
231 /* FIXME: needs to be fixed so that the picture window does not resize itself on the first
232 anamorphic width drop
234 if (fTitle->width - 8 < output_width)
236 displaySize.width *= ((float)output_par_width) / ((float)output_par_height);
239 else // No Anamorphic
241 [fInfoField setStringValue: [NSString stringWithFormat:
242 @"Source: %dx%d, Output: %dx%d", fTitle->width, fTitle->height,
243 fTitle->job->width, fTitle->job->height]];
246 NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
247 if( [self viewNeedsToResizeToSize:viewSize] )
249 [self resizeSheetForViewSize:viewSize];
250 [self setViewSize:viewSize];
253 // Show the scaled text (use the height to check since the width can vary
254 // with anamorphic video).
255 if( ((int)viewSize.height) != fTitle->height )
257 float scale = viewSize.width / ((float)fTitle->width);
258 NSString *scaleString = [NSString stringWithFormat:
259 NSLocalizedString( @" (Preview scaled to %.0f%% actual size)",
260 @"String shown when a preview is scaled" ),
262 [fInfoField setStringValue:
263 [[fInfoField stringValue] stringByAppendingString:scaleString]];
266 [fPrevButton setEnabled: ( fPicture > 0 )];
267 [fNextButton setEnabled: ( fPicture < 9 )];
270 - (IBAction) SettingsChanged: (id) sender
272 hb_job_t * job = fTitle->job;
274 if( [fAnamorphicPopUp indexOfSelectedItem] > 0 )
276 if ([fAnamorphicPopUp indexOfSelectedItem] == 2) // Loose anamorphic
278 job->pixel_ratio = 2;
279 [fWidthStepper setEnabled: YES];
280 [fWidthField setEnabled: YES];
281 /* We set job->width and call hb_set_anamorphic_size in libhb to do a "dry run" to get
282 * the values to be used by libhb for loose anamorphic
284 job->width = [fWidthStepper intValue];
285 hb_set_anamorphic_size(job, &output_width, &output_height, &output_par_width, &output_par_height);
286 [fHeightStepper setIntValue: output_height];
287 [fHeightField setIntValue: output_height];
288 job->height = [fHeightStepper intValue];
291 else // must be "1" or strict anamorphic
293 [fWidthStepper setIntValue: fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3]];
294 [fWidthField setIntValue: fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3]];
296 /* This will show correct anamorphic height values, but
297 show distorted preview picture ratio */
298 [fHeightStepper setIntValue: fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1]];
299 [fHeightField setIntValue: fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1]];
300 job->width = [fWidthStepper intValue];
301 job->height = [fHeightStepper intValue];
303 job->pixel_ratio = 1;
304 [fWidthStepper setEnabled: NO];
305 [fWidthField setEnabled: NO];
308 /* if the sender is the Anamorphic checkbox, record the state
309 of KeepAspect Ratio so it can be reset if Anamorphic is unchecked again */
310 if (sender == fAnamorphicPopUp)
312 keepAspectRatioPreviousState = [fRatioCheck state];
314 [fRatioCheck setState:NSOffState];
315 [fRatioCheck setEnabled: NO];
318 [fHeightStepper setEnabled: NO];
319 [fHeightField setEnabled: NO];
324 job->width = [fWidthStepper intValue];
325 job->height = [fHeightStepper intValue];
326 job->pixel_ratio = 0;
327 [fWidthStepper setEnabled: YES];
328 [fWidthField setEnabled: YES];
329 [fHeightStepper setEnabled: YES];
330 [fHeightField setEnabled: YES];
331 [fRatioCheck setEnabled: YES];
332 /* if the sender is the Anamorphic checkbox, we return the
333 keep AR checkbox to its previous state */
334 if (sender == fAnamorphicPopUp)
336 [fRatioCheck setState:keepAspectRatioPreviousState];
342 job->keep_ratio = ( [fRatioCheck state] == NSOnState );
344 fPictureFilterSettings.deinterlace = [fDeinterlacePopUp indexOfSelectedItem];
345 /* if the gui deinterlace settings are fast through slowest, the job->deinterlace
346 value needs to be set to one, for the job as well as the previews showing deinterlacing
347 otherwise set job->deinterlace to 0 or "off" */
348 if (fPictureFilterSettings.deinterlace > 0)
350 job->deinterlace = 1;
354 job->deinterlace = 0;
356 fPictureFilterSettings.denoise = [fDenoisePopUp indexOfSelectedItem];
357 fPictureFilterSettings.vfr = [fVFRCheck state];
358 if (fPictureFilterSettings.vfr > 0)
360 [fDetelecineCheck setState:NSOnState];
361 [fDetelecineCheck setEnabled: NO];
365 [fDetelecineCheck setEnabled: YES];
367 fPictureFilterSettings.detelecine = [fDetelecineCheck state];
368 fPictureFilterSettings.deblock = [fDeblockCheck state];
369 //job->pixel_ratio = ( [fPARCheck state] == NSOnState );
371 autoCrop = ( [fCropMatrix selectedRow] == 0 );
372 [fCropTopStepper setEnabled: !autoCrop];
373 [fCropBottomStepper setEnabled: !autoCrop];
374 [fCropLeftStepper setEnabled: !autoCrop];
375 [fCropRightStepper setEnabled: !autoCrop];
379 memcpy( job->crop, fTitle->crop, 4 * sizeof( int ) );
383 job->crop[0] = [fCropTopStepper intValue];
384 job->crop[1] = [fCropBottomStepper intValue];
385 job->crop[2] = [fCropLeftStepper intValue];
386 job->crop[3] = [fCropRightStepper intValue];
389 if( job->keep_ratio )
391 if( sender == fWidthStepper || sender == fRatioCheck ||
392 sender == fCropTopStepper || sender == fCropBottomStepper )
394 hb_fix_aspect( job, HB_KEEP_WIDTH );
395 if( job->height > fTitle->height )
397 job->height = fTitle->height;
398 hb_fix_aspect( job, HB_KEEP_HEIGHT );
403 hb_fix_aspect( job, HB_KEEP_HEIGHT );
404 if( job->width > fTitle->width )
406 job->width = fTitle->width;
407 hb_fix_aspect( job, HB_KEEP_WIDTH );
412 [fWidthStepper setIntValue: job->width];
413 [fWidthField setIntValue: job->width];
414 if( [fAnamorphicPopUp indexOfSelectedItem] < 2 )
416 [fHeightStepper setIntValue: job->height];
417 [fHeightField setIntValue: job->height];
419 [fCropTopStepper setIntValue: job->crop[0]];
420 [fCropTopField setIntValue: job->crop[0]];
421 [fCropBottomStepper setIntValue: job->crop[1]];
422 [fCropBottomField setIntValue: job->crop[1]];
423 [fCropLeftStepper setIntValue: job->crop[2]];
424 [fCropLeftField setIntValue: job->crop[2]];
425 [fCropRightStepper setIntValue: job->crop[3]];
426 [fCropRightField setIntValue: job->crop[3]];
427 /* Sanity Check Here for < 16 px preview to avoid
428 crashing hb_get_preview. In fact, just for kicks
429 lets getting previews at a min limit of 32, since
430 no human can see any meaningful detail below that */
431 if (job->width >= 64 && job->height >= 64)
433 [self Display: HB_ANIMATE_NONE];
437 - (IBAction) PreviousPicture: (id) sender
444 [self Display: HB_ANIMATE_BACKWARD];
447 - (IBAction) NextPicture: (id) sender
454 [self Display: HB_ANIMATE_FORWARD];
457 - (IBAction) ClosePanel: (id) sender
459 if ([delegate respondsToSelector:@selector(pictureSettingsDidChange)])
460 [delegate pictureSettingsDidChange];
462 [NSApp endSheet: fPicturePanel];
463 [fPicturePanel orderOut: self];
470 - (void) setAutoCrop: (BOOL) setting
475 - (BOOL) allowLooseAnamorphic
477 return allowLooseAnamorphic;
480 - (void) setAllowLooseAnamorphic: (BOOL) setting
482 allowLooseAnamorphic = setting;
487 return fPictureFilterSettings.detelecine;
490 - (void) setDetelecine: (int) setting
492 fPictureFilterSettings.detelecine = setting;
497 return fPictureFilterSettings.vfr;
500 - (void) setVFR: (int) setting
502 fPictureFilterSettings.vfr = setting;
507 return fPictureFilterSettings.deinterlace;
510 - (void) setDeinterlace: (int) setting {
511 fPictureFilterSettings.deinterlace = setting;
516 return fPictureFilterSettings.denoise;
519 - (void) setDenoise: (int) setting
521 fPictureFilterSettings.denoise = setting;
526 return fPictureFilterSettings.deblock;
529 - (void) setDeblock: (int) setting
531 fPictureFilterSettings.deblock = setting;
534 - (void)showPanelInWindow: (NSWindow *)fWindow forTitle: (hb_title_t *)title
536 [self SetTitle:title];
538 [NSApp beginSheet:fPicturePanel
539 modalForWindow:fWindow
545 - (BOOL) loadMyNibFile
547 if(![NSBundle loadNibNamed:@"PictureSettings" owner:self])
549 NSLog(@"Warning! Could not load myNib file.\n");
558 @implementation PictureController (Private)
561 // -[PictureController(Private) optimalViewSizeForImageSize:]
563 // Given the size of the preview image to be shown, returns the best possible
564 // size for the OpenGL view.
566 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize
568 // The min size is 320x240
569 float minWidth = 320.0;
570 float minHeight = 240.0;
572 // The max size of the view is when the sheet is taking up 85% of the screen.
573 NSSize screenSize = [[NSScreen mainScreen] frame].size;
574 NSSize sheetSize = [fPicturePanel frame].size;
575 NSSize viewAreaSize = [fPictureGLViewArea frame].size;
576 float paddingX = sheetSize.width - viewAreaSize.width;
577 float paddingY = sheetSize.height - viewAreaSize.height;
578 float maxWidth = (0.85 * screenSize.width) - paddingX;
579 float maxHeight = (0.85 * screenSize.height) - paddingY;
581 NSSize resultSize = imageSize;
583 // Its better to have a view that's too small than a view that's too big, so
584 // apply the maximum constraints last.
585 if( resultSize.width < minWidth )
587 resultSize.height *= (minWidth / resultSize.width);
588 resultSize.width = minWidth;
590 if( resultSize.height < minHeight )
592 resultSize.width *= (minHeight / resultSize.height);
593 resultSize.height = minHeight;
595 if( resultSize.width > maxWidth )
597 resultSize.height *= (maxWidth / resultSize.width);
598 resultSize.width = maxWidth;
600 if( resultSize.height > maxHeight )
602 resultSize.width *= (maxHeight / resultSize.height);
603 resultSize.height = maxHeight;
610 // -[PictureController(Private) resizePanelForViewSize:animate:]
612 // Resizes the entire sheet to accomodate an OpenGL view of a particular size.
614 - (void)resizeSheetForViewSize: (NSSize)viewSize
616 // Figure out the deltas for the new frame area
617 NSSize currentSize = [fPictureGLViewArea frame].size;
618 float deltaX = viewSize.width - currentSize.width;
619 float deltaY = viewSize.height - currentSize.height;
621 // Now resize the whole panel by those same deltas, but don't exceed the min
622 NSRect frame = [fPicturePanel frame];
623 NSSize maxSize = [fPicturePanel maxSize];
624 NSSize minSize = [fPicturePanel minSize];
625 frame.size.width += deltaX;
626 frame.size.height += deltaY;
627 if( frame.size.width < minSize.width )
629 frame.size.width = minSize.width;
631 if( frame.size.height < minSize.height )
633 frame.size.height = minSize.height;
636 // But now the sheet is off-center, so also shift the origin to center it and
637 // keep the top aligned.
638 frame.origin.x -= (deltaX / 2.0);
639 frame.origin.y -= deltaY;
641 [fPicturePanel setFrame:frame display:YES animate:YES];
645 // -[PictureController(Private) setViewSize:]
647 // Changes the OpenGL view's size and centers it vertially inside of its area.
648 // Assumes resizeSheetForViewSize: has already been called.
650 - (void)setViewSize: (NSSize)viewSize
652 [fPictureGLView setFrameSize:viewSize];
654 // center it vertically
655 NSPoint origin = [fPictureGLViewArea frame].origin;
656 origin.y += ([fPictureGLViewArea frame].size.height -
657 [fPictureGLView frame].size.height) / 2.0;
658 [fPictureGLView setFrameOrigin:origin];
662 // -[PictureController(Private) viewNeedsToResizeToSize:]
664 // Returns YES if the view will need to resize to match the given size.
666 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize
668 NSSize viewSize = [fPictureGLView frame].size;
669 return (newSize.width != viewSize.width || newSize.height != viewSize.height);