X-Git-Url: http://git.osdn.jp/view?a=blobdiff_plain;f=macosx%2FPictureController.mm;h=c32a120369db8e2358a488cb8126f4df88335bd5;hb=533776bbad20db93fe964bc69975f108b2a30888;hp=424e8e96716838abe9b2fe12a68d4f9467de6ac5;hpb=5a33f5d08deefc0c0cc6adf8314d391e50a472fc;p=handbrake-jp%2Fhandbrake-jp-git.git diff --git a/macosx/PictureController.mm b/macosx/PictureController.mm index 424e8e96..c32a1203 100644 --- a/macosx/PictureController.mm +++ b/macosx/PictureController.mm @@ -1,45 +1,52 @@ /* $Id: PictureController.mm,v 1.11 2005/08/01 15:10:44 titer Exp $ This file is part of the HandBrake source code. - Homepage: . + Homepage: . It may be used under the terms of the GNU General Public License. */ -#include "PictureController.h" +#import "PictureController.h" -static int GetAlignedSize( int size ) -{ - int result = 1; - while( result < size ) - { - result *= 2; - } - return result; -} +@interface PictureController (Private) + +- (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize; +- (void)resizeSheetForViewSize: (NSSize)viewSize; +- (void)setViewSize: (NSSize)viewSize; +- (BOOL)viewNeedsToResizeToSize: (NSSize)newSize; + +@end @implementation PictureController - (id)initWithDelegate:(id)del { - if (self = [super init]) + if (self = [super initWithWindowNibName:@"PictureSettings"]) { + // NSWindowController likes to lazily load its window. However since + // this controller tries to set all sorts of outlets before the window + // is displayed, we need it to load immediately. The correct way to do + // this, according to the documentation, is simply to invoke the window + // getter once. + // + // If/when we switch a lot of this stuff to bindings, this can probably + // go away. + [self window]; + delegate = del; - [self loadMyNibFile]; + fPicturePreviews = [[NSMutableDictionary dictionaryWithCapacity: HB_NUM_HBLIB_PICTURES] retain]; } return self; } +- (void) dealloc +{ + [fPicturePreviews release]; + [super dealloc]; +} + - (void) SetHandle: (hb_handle_t *) handle { fHandle = handle; - fHasQE = CGDisplayUsesOpenGLAcceleration( kCGDirectMainDisplay ); - - fBuffer = NULL; - fBufferSize = 0; - fTexBuf[0] = NULL; - fTexBuf[1] = NULL; - fTexBufSize = 0; - [fWidthStepper setValueWraps: NO]; [fWidthStepper setIncrement: 16]; [fWidthStepper setMinValue: 64]; @@ -63,27 +70,6 @@ static int GetAlignedSize( int size ) fTitle = title; - /* Make sure we have big enough buffers */ - int newSize; - newSize = ( title->width + 2 ) * (title->height + 2 ) * 4; - if( fBufferSize < newSize ) - { - fBufferSize = newSize; - fBuffer = (uint8_t *) realloc( fBuffer, fBufferSize ); - } - if( !fHasQE ) - { - newSize = ( GetAlignedSize( title->width + 2 ) * - GetAlignedSize( title->height + 2 ) * 4 ); - } - if( fTexBufSize < newSize ) - { - fTexBufSize = newSize; - fTexBuf[0] = (uint8_t *) realloc( fTexBuf[0], fTexBufSize ); - fTexBuf[1] = (uint8_t *) realloc( fTexBuf[1], fTexBufSize ); - } - - [fWidthStepper setMaxValue: title->width]; [fWidthStepper setIntValue: job->width]; [fWidthField setIntValue: job->width]; @@ -95,22 +81,19 @@ static int GetAlignedSize( int size ) [fCropBottomStepper setMaxValue: title->height/2-2]; [fCropLeftStepper setMaxValue: title->width/2-2]; [fCropRightStepper setMaxValue: title->width/2-2]; + + /* Populate the Anamorphic NSPopUp button here */ + [fAnamorphicPopUp removeAllItems]; + [fAnamorphicPopUp addItemWithTitle: @"None"]; + [fAnamorphicPopUp addItemWithTitle: @"Strict"]; + if (allowLooseAnamorphic) + { + [fAnamorphicPopUp addItemWithTitle: @"Loose"]; + } + [fAnamorphicPopUp selectItemAtIndex: job->pixel_ratio]; - - /* we use a popup to show the deinterlace settings */ - [fDeinterlacePopUp removeAllItems]; - [fDeinterlacePopUp addItemWithTitle: @"None"]; - [fDeinterlacePopUp addItemWithTitle: @"Fast"]; - [fDeinterlacePopUp addItemWithTitle: @"Slow"]; - [fDeinterlacePopUp addItemWithTitle: @"Slower"]; - [fDeinterlacePopUp addItemWithTitle: @"Slowest"]; - - /* Set deinterlaces level according to the integer in the main window */ - [fDeinterlacePopUp selectItemAtIndex: fPictureFilterSettings.deinterlace]; - - - [fPARCheck setState: job->pixel_ratio ? NSOnState : NSOffState]; - + /* We initially set the previous state of keep ar to on */ + keepAspectRatioPreviousState = 1; if (!autoCrop) { [fCropMatrix selectCellAtRow: 1 column:0]; @@ -129,20 +112,32 @@ static int GetAlignedSize( int size ) [fCropMatrix selectCellAtRow: 0 column:0]; } - /* set the detelecine state according to the state in main window */ - /* if framerate is 23.976 we do not allow detelecine, otherwise, enable and set according to fDetelecineMainWindow outlet */ - if (fTitle->rate_base == 1126125) - { - [fDetelecineCheck setEnabled: NO]; - [fDetelecineCheck setState: NSOffState]; - - } - else - { - [fDetelecineCheck setEnabled: YES]; - [fDetelecineCheck setState: fPictureFilterSettings.detelecine]; - } - + /* Set filters widgets according to the filters struct */ + [fDetelecineCheck setState:fPictureFilterSettings.detelecine]; + [fDeinterlacePopUp selectItemAtIndex: fPictureFilterSettings.deinterlace]; + [fDenoisePopUp selectItemAtIndex: fPictureFilterSettings.denoise]; + [fDeblockCheck setState: fPictureFilterSettings.deblock]; + + fPicture = 0; + MaxOutputWidth = title->width - job->crop[2] - job->crop[3]; + MaxOutputHeight = title->height - job->crop[0] - job->crop[1]; + [self SettingsChanged: nil]; +} + +/* we use this to setup the initial picture filters upon first launch, after that their states +are maintained across different sources */ +- (void) setInitialPictureFilters +{ + /* we use a popup to show the deinterlace settings */ + [fDeinterlacePopUp removeAllItems]; + [fDeinterlacePopUp addItemWithTitle: @"None"]; + [fDeinterlacePopUp addItemWithTitle: @"Fast"]; + [fDeinterlacePopUp addItemWithTitle: @"Slow"]; + [fDeinterlacePopUp addItemWithTitle: @"Slower"]; + + /* Set deinterlaces level according to the integer in the main window */ + [fDeinterlacePopUp selectItemAtIndex: fPictureFilterSettings.deinterlace]; + /* we use a popup to show the denoise settings */ [fDenoisePopUp removeAllItems]; [fDenoisePopUp addItemWithTitle: @"None"]; @@ -151,147 +146,220 @@ static int GetAlignedSize( int size ) [fDenoisePopUp addItemWithTitle: @"Strong"]; /* Set denoises level according to the integer in the main window */ [fDenoisePopUp selectItemAtIndex: fPictureFilterSettings.denoise]; - - MaxOutputWidth = job->width; - MaxOutputHeight = job->height; - fPicture = 0; - [self SettingsChanged: nil]; + + /* we use a popup to show the decomb settings */ + [fDecombPopUp removeAllItems]; + [fDecombPopUp addItemWithTitle: @"None"]; + [fDecombPopUp addItemWithTitle: @"Default"]; + [fDecombPopUp addItemWithTitle: @"Custom"]; + /* Set denoises level according to the integer in the main window */ + [fDecombPopUp selectItemAtIndex: fPictureFilterSettings.decomb]; + } -- (void) Display: (int) anim +// Adjusts the window to draw the current picture (fPicture) adjusting its size as +// necessary to display as much of the picture as possible. +- (void) displayPreview { - hb_get_preview( fHandle, fTitle, fPicture, fBuffer ); - - /* Backup previous picture (for effects) */ - memcpy( fTexBuf[1], fTexBuf[0], fTexBufSize ); - - if( fHasQE ) + [fPictureView setImage: [self imageForPicture: fPicture]]; + + NSSize displaySize = NSMakeSize( ( CGFloat )fTitle->width, ( CGFloat )fTitle->height ); + /* Set the picture size display fields below the Preview Picture*/ + if( fTitle->job->pixel_ratio == 1 ) // Original PAR Implementation { - /* Simply copy */ - memcpy( fTexBuf[0], fBuffer, fTexBufSize ); + output_width = fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3]; + output_height = fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1]; + display_width = output_width * fTitle->job->pixel_aspect_width / fTitle->job->pixel_aspect_height; + [fInfoField setStringValue:[NSString stringWithFormat: + @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d", + fTitle->width, fTitle->height, output_width, output_height, display_width, output_height]]; + displaySize.width *= ( ( CGFloat )fTitle->job->pixel_aspect_width ) / ( ( CGFloat )fTitle->job->pixel_aspect_height ); } - else + else if (fTitle->job->pixel_ratio == 2) // Loose Anamorphic { - /* Copy line by line */ - uint8_t * in = fBuffer; - uint8_t * out = fTexBuf[0]; - - for( int i = fTitle->height + 2; i--; ) - { - memcpy( out, in, 4 * ( fTitle->width + 2 ) ); - in += 4 * ( fTitle->width + 2 ); - out += 4 * GetAlignedSize( fTitle->width + 2 ); - } - + display_width = output_width * output_par_width / output_par_height; + [fInfoField setStringValue:[NSString stringWithFormat: + @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d", + fTitle->width, fTitle->height, output_width, output_height, display_width, output_height]]; + + /* FIXME: use the original aspect ratio to calculate the displaySize, + probably the size will not be the right one, + but at least the windows does not resize every time. */ + displaySize.width *= ( ( CGFloat )fTitle->job->pixel_aspect_width) / ( ( CGFloat )fTitle->job->pixel_aspect_height ); } - - if( [fEffectsCheck state] == NSOffState ) + else // No Anamorphic { - anim = HB_ANIMATE_NONE; + [fInfoField setStringValue: [NSString stringWithFormat: + @"Source: %dx%d, Output: %dx%d", fTitle->width, fTitle->height, + fTitle->job->width, fTitle->job->height]]; } - else if( [[NSApp currentEvent] modifierFlags] & NSShiftKeyMask ) + + NSSize viewSize = [self optimalViewSizeForImageSize:displaySize]; + if( [self viewNeedsToResizeToSize:viewSize] ) { - anim |= HB_ANIMATE_SLOW; + [self resizeSheetForViewSize:viewSize]; + [self setViewSize:viewSize]; } - [fPictureGLView Display: anim buffer1: fTexBuf[0] - buffer2: fTexBuf[1] width: ( fTitle->width + 2 ) - height: ( fTitle->height + 2 )]; - - /* Set the Output Display below the Preview Picture*/ - int titlewidth = fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3]; - int arpwidth = fTitle->job->pixel_aspect_width; - int arpheight = fTitle->job->pixel_aspect_height; - int displayparwidth = titlewidth * arpwidth / arpheight; - int displayparheight = fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1]; - if (fTitle->job->pixel_ratio == 1) - { - - [fInfoField setStringValue: [NSString stringWithFormat: - @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d", fTitle->width, fTitle->height, - titlewidth, displayparheight, displayparwidth, - displayparheight]]; - - - } - else - { - [fInfoField setStringValue: [NSString stringWithFormat: - @"Source: %dx%d, Output: %dx%d", fTitle->width, fTitle->height, - fTitle->job->width, fTitle->job->height]]; - } - + // Show the scaled text (use the height to check since the width can vary + // with anamorphic video). + if( ( ( int )viewSize.height ) != fTitle->height ) + { + CGFloat scale = viewSize.width / ( ( CGFloat ) fTitle->width ); + NSString *scaleString = [NSString stringWithFormat: + NSLocalizedString( @" (Preview scaled to %.0f%% actual size)", + @"String shown when a preview is scaled" ), + scale * 100.0]; + [fInfoField setStringValue: [[fInfoField stringValue] stringByAppendingString:scaleString]]; + } [fPrevButton setEnabled: ( fPicture > 0 )]; [fNextButton setEnabled: ( fPicture < 9 )]; } +- (IBAction) deblockSliderChanged: (id) sender +{ + if ([fDeblockSlider floatValue] == 4.0) + { + [fDeblockField setStringValue: [NSString stringWithFormat: @"Off"]]; + } + else + { + [fDeblockField setStringValue: [NSString stringWithFormat: @"%.0f", [fDeblockSlider floatValue]]]; + } + [self SettingsChanged: sender]; +} + - (IBAction) SettingsChanged: (id) sender { hb_job_t * job = fTitle->job; - if ([fPARCheck state] == 1 ) + autoCrop = ( [fCropMatrix selectedRow] == 0 ); + [fCropTopStepper setEnabled: !autoCrop]; + [fCropBottomStepper setEnabled: !autoCrop]; + [fCropLeftStepper setEnabled: !autoCrop]; + [fCropRightStepper setEnabled: !autoCrop]; + + if( autoCrop ) + { + memcpy( job->crop, fTitle->crop, 4 * sizeof( int ) ); + } + else + { + job->crop[0] = [fCropTopStepper intValue]; + job->crop[1] = [fCropBottomStepper intValue]; + job->crop[2] = [fCropLeftStepper intValue]; + job->crop[3] = [fCropRightStepper intValue]; + } + + if( [fAnamorphicPopUp indexOfSelectedItem] > 0 ) { - [fWidthStepper setIntValue: MaxOutputWidth]; - [fWidthField setIntValue: MaxOutputWidth]; + if ([fAnamorphicPopUp indexOfSelectedItem] == 2) // Loose anamorphic + { + job->pixel_ratio = 2; + [fWidthStepper setEnabled: YES]; + [fWidthField setEnabled: YES]; + /* We set job->width and call hb_set_anamorphic_size in libhb to do a "dry run" to get + * the values to be used by libhb for loose anamorphic + */ + /* if the sender is the anamorphic popup, then we know that loose anamorphic has just + * been turned on, so snap the width to full width for the source. + */ + if (sender == fAnamorphicPopUp) + { + [fWidthStepper setIntValue: fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3]]; + [fWidthField setIntValue: fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3]]; + } + job->width = [fWidthStepper intValue]; + hb_set_anamorphic_size(job, &output_width, &output_height, &output_par_width, &output_par_height); + [fHeightStepper setIntValue: output_height]; + [fHeightField setIntValue: output_height]; + job->height = [fHeightStepper intValue]; + + } + else // must be "1" or strict anamorphic + { + [fWidthStepper setIntValue: fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3]]; + [fWidthField setIntValue: fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3]]; + + /* This will show correct anamorphic height values, but + show distorted preview picture ratio */ + [fHeightStepper setIntValue: fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1]]; + [fHeightField setIntValue: fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1]]; + job->width = [fWidthStepper intValue]; + job->height = [fHeightStepper intValue]; + + job->pixel_ratio = 1; + [fWidthStepper setEnabled: NO]; + [fWidthField setEnabled: NO]; + } - /* This will show correct anamorphic height values, but - show distorted preview picture ratio */ - [fHeightStepper setIntValue: fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1]]; - [fHeightField setIntValue: fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1]]; + /* if the sender is the Anamorphic checkbox, record the state + of KeepAspect Ratio so it can be reset if Anamorphic is unchecked again */ + if (sender == fAnamorphicPopUp) + { + keepAspectRatioPreviousState = [fRatioCheck state]; + } + [fRatioCheck setState:NSOffState]; + [fRatioCheck setEnabled: NO]; - /* This will show wrong anamorphic height values, but - show proper preview picture ratio */ - //[fHeightStepper setIntValue: MaxOutputHeight]; - //[fHeightField setIntValue: MaxOutputHeight]; - [fRatioCheck setState: 0]; - [fWidthStepper setEnabled: NO]; - [fWidthField setEnabled: NO]; [fHeightStepper setEnabled: NO]; [fHeightField setEnabled: NO]; - [fRatioCheck setEnabled: NO]; + } - - else + else { + job->width = [fWidthStepper intValue]; + job->height = [fHeightStepper intValue]; + job->pixel_ratio = 0; [fWidthStepper setEnabled: YES]; [fWidthField setEnabled: YES]; [fHeightStepper setEnabled: YES]; [fHeightField setEnabled: YES]; [fRatioCheck setEnabled: YES]; + /* if the sender is the Anamorphic checkbox, we return the + keep AR checkbox to its previous state */ + if (sender == fAnamorphicPopUp) + { + [fRatioCheck setState:keepAspectRatioPreviousState]; + } + } - job->width = [fWidthStepper intValue]; - job->height = [fHeightStepper intValue]; job->keep_ratio = ( [fRatioCheck state] == NSOnState ); + fPictureFilterSettings.deinterlace = [fDeinterlacePopUp indexOfSelectedItem]; + /* if the gui deinterlace settings are fast through slowest, the job->deinterlace + value needs to be set to one, for the job as well as the previews showing deinterlacing + otherwise set job->deinterlace to 0 or "off" */ + if (fPictureFilterSettings.deinterlace > 0) + { + job->deinterlace = 1; + } + else + { + job->deinterlace = 0; + } fPictureFilterSettings.denoise = [fDenoisePopUp indexOfSelectedItem]; + fPictureFilterSettings.detelecine = [fDetelecineCheck state]; - job->pixel_ratio = ( [fPARCheck state] == NSOnState ); - - autoCrop = ( [fCropMatrix selectedRow] == 0 ); - [fCropTopStepper setEnabled: !autoCrop]; - [fCropBottomStepper setEnabled: !autoCrop]; - [fCropLeftStepper setEnabled: !autoCrop]; - [fCropRightStepper setEnabled: !autoCrop]; -// [fAutoCropMainWindow setStringValue: [NSString stringWithFormat:@"%d",autocrop]]; - if( autoCrop ) + + if ([fDeblockField stringValue] == @"Off") { - memcpy( job->crop, fTitle->crop, 4 * sizeof( int ) ); + fPictureFilterSettings.deblock = 0; } else { - job->crop[0] = [fCropTopStepper intValue]; - job->crop[1] = [fCropBottomStepper intValue]; - job->crop[2] = [fCropLeftStepper intValue]; - job->crop[3] = [fCropRightStepper intValue]; + fPictureFilterSettings.deblock = [fDeblockField intValue]; } + + fPictureFilterSettings.decomb = [fDecombPopUp indexOfSelectedItem]; if( job->keep_ratio ) { if( sender == fWidthStepper || sender == fRatioCheck || - sender == fCropTopStepper || sender == fCropBottomStepper ) + sender == fCropTopStepper || sender == fCropBottomStepper ) { hb_fix_aspect( job, HB_KEEP_WIDTH ); if( job->height > fTitle->height ) @@ -309,12 +377,22 @@ static int GetAlignedSize( int size ) hb_fix_aspect( job, HB_KEEP_WIDTH ); } } + // hb_get_preview can't handle sizes that are larger than the original title + // dimensions + if( job->width > fTitle->width ) + job->width = fTitle->width; + + if( job->height > fTitle->height ) + job->height = fTitle->height; } - + [fWidthStepper setIntValue: job->width]; [fWidthField setIntValue: job->width]; - [fHeightStepper setIntValue: job->height]; - [fHeightField setIntValue: job->height]; + if( [fAnamorphicPopUp indexOfSelectedItem] < 2 ) + { + [fHeightStepper setIntValue: job->height]; + [fHeightField setIntValue: job->height]; + } [fCropTopStepper setIntValue: job->crop[0]]; [fCropTopField setIntValue: job->crop[0]]; [fCropBottomStepper setIntValue: job->crop[1]]; @@ -324,12 +402,15 @@ static int GetAlignedSize( int size ) [fCropRightStepper setIntValue: job->crop[3]]; [fCropRightField setIntValue: job->crop[3]]; /* Sanity Check Here for < 16 px preview to avoid - crashing hb_get_preview. In fact, just for kicks - lets getting previews at a min limit of 32, since - no human can see any meaningful detail below that */ + crashing hb_get_preview. In fact, just for kicks + lets getting previews at a min limit of 32, since + no human can see any meaningful detail below that */ if (job->width >= 64 && job->height >= 64) { - [self Display: HB_ANIMATE_NONE]; + // Purge the existing picture previews so they get recreated the next time + // they are needed. + [self purgeImageCache]; + [self displayPreview]; } } @@ -340,7 +421,7 @@ static int GetAlignedSize( int size ) return; } fPicture--; - [self Display: HB_ANIMATE_BACKWARD]; + [self displayPreview]; } - (IBAction) NextPicture: (id) sender @@ -350,16 +431,16 @@ static int GetAlignedSize( int size ) return; } fPicture++; - [self Display: HB_ANIMATE_FORWARD]; + [self displayPreview]; } - (IBAction) ClosePanel: (id) sender { if ([delegate respondsToSelector:@selector(pictureSettingsDidChange)]) [delegate pictureSettingsDidChange]; - - [NSApp endSheet: fPicturePanel]; - [fPicturePanel orderOut: self]; + + [NSApp endSheet:[self window]]; + [[self window] orderOut:self]; } - (BOOL) autoCrop @@ -371,6 +452,16 @@ static int GetAlignedSize( int size ) autoCrop = setting; } +- (BOOL) allowLooseAnamorphic +{ + return allowLooseAnamorphic; +} + +- (void) setAllowLooseAnamorphic: (BOOL) setting +{ + allowLooseAnamorphic = setting; +} + - (int) detelecine { return fPictureFilterSettings.detelecine; @@ -389,7 +480,14 @@ static int GetAlignedSize( int size ) - (void) setDeinterlace: (int) setting { fPictureFilterSettings.deinterlace = setting; } +- (int) decomb +{ + return fPictureFilterSettings.decomb; +} +- (void) setDecomb: (int) setting { + fPictureFilterSettings.decomb = setting; +} - (int) denoise { return fPictureFilterSettings.denoise; @@ -400,27 +498,315 @@ static int GetAlignedSize( int size ) fPictureFilterSettings.denoise = setting; } -- (void) showPanelInWindow: (NSWindow *) fWindow forTitle:(hb_title_t *)title { - NSSize newSize; - newSize.width = 246 + title->width; - newSize.height = 80 + title->height; - [fPicturePanel setContentSize: newSize]; +- (int) deblock +{ + return fPictureFilterSettings.deblock; +} + +- (void) setDeblock: (int) setting +{ + fPictureFilterSettings.deblock = setting; +} - [self SetTitle: title]; +- (void)showPanelInWindow: (NSWindow *)fWindow forTitle: (hb_title_t *)title +{ + [self SetTitle:title]; - [NSApp beginSheet: fPicturePanel modalForWindow: fWindow - modalDelegate: NULL didEndSelector: NULL contextInfo: NULL]; + [NSApp beginSheet:[self window] + modalForWindow:fWindow + modalDelegate:nil + didEndSelector:nil + contextInfo:NULL]; } -- (BOOL) loadMyNibFile + +// This function converts an image created by libhb (specified via pictureIndex) into +// an NSImage suitable for the GUI code to use. If removeBorders is YES, +// makeImageForPicture crops the image generated by libhb stripping off the gray +// border around the content. This is the low-level method that generates the image. +// -imageForPicture calls this function whenever it can't find an image in its cache. ++ (NSImage *) makeImageForPicture: (int)pictureIndex + libhb:(hb_handle_t*)handle + title:(hb_title_t*)title + removeBorders:(BOOL)removeBorders { - if(![NSBundle loadNibNamed:@"PictureSettings" owner:self]) + if (removeBorders) { - NSLog(@"Warning! Could not load myNib file.\n"); - return NO; + // |<---------- title->width ----------->| + // | |<---- title->job->width ---->| | + // | | | | + // ....................................... + // ....+-----------------------------+.... + // ....| |....<-- gray border + // ....| |.... + // ....| |.... + // ....| |<------- image + // ....| |.... + // ....| |.... + // ....| |.... + // ....| |.... + // ....| |.... + // ....+-----------------------------+.... + // ....................................... + + static uint8_t * buffer; + static int bufferSize; + + // Make sure we have a big enough buffer to receive the image from libhb. libhb + // creates images with a one-pixel border around the original content. Hence we + // add 2 pixels horizontally and vertically to the buffer size. + int srcWidth = title->width + 2; + int srcHeight= title->height + 2; + int newSize; + newSize = srcWidth * srcHeight * 4; + if( bufferSize < newSize ) + { + bufferSize = newSize; + buffer = (uint8_t *) realloc( buffer, bufferSize ); + } + + hb_get_preview( handle, title, pictureIndex, buffer ); + + // Create an NSBitmapImageRep and copy the libhb image into it, converting it from + // libhb's format to one suitable for NSImage. Along the way, we'll strip off the + // border around libhb's image. + + // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format. + // Alpha is ignored. + + int dstWidth = title->job->width; + int dstHeight = title->job->height; + NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat; + NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc] + initWithBitmapDataPlanes:nil + pixelsWide:dstWidth + pixelsHigh:dstHeight + bitsPerSample:8 + samplesPerPixel:3 // ignore alpha + hasAlpha:NO + isPlanar:NO + colorSpaceName:NSCalibratedRGBColorSpace + bitmapFormat:bitmapFormat + bytesPerRow:dstWidth * 4 + bitsPerPixel:32] autorelease]; + + int borderTop = (srcHeight - dstHeight) / 2; + int borderLeft = (srcWidth - dstWidth) / 2; + + UInt32 * src = (UInt32 *)buffer; + UInt32 * dst = (UInt32 *)[imgrep bitmapData]; + src += borderTop * srcWidth; // skip top rows in src to get to first row of dst + src += borderLeft; // skip left pixels in src to get to first pixel of dst + for (int r = 0; r < dstHeight; r++) + { + for (int c = 0; c < dstWidth; c++) +#if TARGET_RT_LITTLE_ENDIAN + *dst++ = Endian32_Swap(*src++); +#else + *dst++ = *src++; +#endif + src += (srcWidth - dstWidth); // skip to next row in src + } + + NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(dstWidth, dstHeight)] autorelease]; + [img addRepresentation:imgrep]; + + return img; + } + else + { + // Make sure we have big enough buffer + static uint8_t * buffer; + static int bufferSize; + + int newSize; + newSize = ( title->width + 2 ) * (title->height + 2 ) * 4; + if( bufferSize < newSize ) + { + bufferSize = newSize; + buffer = (uint8_t *) realloc( buffer, bufferSize ); + } + + hb_get_preview( handle, title, pictureIndex, buffer ); + + // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format. + // We'll copy that into an NSImage swapping it to ARGB in the process. Alpha is + // ignored. + int width = title->width + 2; // hblib adds a one-pixel border to the image + int height = title->height + 2; + int numPixels = width * height; + NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat; + NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc] + initWithBitmapDataPlanes:nil + pixelsWide:width + pixelsHigh:height + bitsPerSample:8 + samplesPerPixel:3 // ignore alpha + hasAlpha:NO + isPlanar:NO + colorSpaceName:NSCalibratedRGBColorSpace + bitmapFormat:bitmapFormat + bytesPerRow:width * 4 + bitsPerPixel:32] autorelease]; + + UInt32 * src = (UInt32 *)buffer; + UInt32 * dst = (UInt32 *)[imgrep bitmapData]; + for (int i = 0; i < numPixels; i++) +#if TARGET_RT_LITTLE_ENDIAN + *dst++ = Endian32_Swap(*src++); +#else + *dst++ = *src++; +#endif + + NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(width, height)] autorelease]; + [img addRepresentation:imgrep]; + + return img; + } +} + +// Returns the preview image for the specified index, retrieving it from its internal +// cache or by calling makeImageForPicture if it is not cached. Generally, you should +// use imageForPicture so that images are cached. Calling makeImageForPicture will +// always generate a new copy of the image. +- (NSImage *) imageForPicture: (int) pictureIndex +{ + // The preview for the specified index may not currently exist, so this method + // generates it if necessary. + NSString * key = [NSString stringWithFormat:@"%d", pictureIndex]; + NSImage * theImage = [fPicturePreviews objectForKey:key]; + if (!theImage) + { + theImage = [PictureController makeImageForPicture:pictureIndex libhb:fHandle title:fTitle removeBorders: NO]; + [fPicturePreviews setObject:theImage forKey:key]; + } + return theImage; +} + +// Purges all images from the cache. The next call to imageForPicture will cause a new +// image to be generated. +- (void) purgeImageCache +{ + [fPicturePreviews removeAllObjects]; +} + +@end + +@implementation PictureController (Private) + +// +// -[PictureController(Private) optimalViewSizeForImageSize:] +// +// Given the size of the preview image to be shown, returns the best possible +// size for the view. +// +- (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize +{ + // The min size is 320x240 + CGFloat minWidth = 320.0; + CGFloat minHeight = 240.0; + + // The max size of the view is when the sheet is taking up 85% of the screen. + NSSize screenSize = [[NSScreen mainScreen] frame].size; + NSSize sheetSize = [[self window] frame].size; + NSSize viewAreaSize = [fPictureViewArea frame].size; + CGFloat paddingX = sheetSize.width - viewAreaSize.width; + CGFloat paddingY = sheetSize.height - viewAreaSize.height; + CGFloat maxWidth = (0.85 * screenSize.width) - paddingX; + CGFloat maxHeight = (0.85 * screenSize.height) - paddingY; + + NSSize resultSize = imageSize; + + // Its better to have a view that's too small than a view that's too big, so + // apply the maximum constraints last. + if( resultSize.width < minWidth ) + { + resultSize.height *= (minWidth / resultSize.width); + resultSize.width = minWidth; + } + if( resultSize.height < minHeight ) + { + resultSize.width *= (minHeight / resultSize.height); + resultSize.height = minHeight; + } + if( resultSize.width > maxWidth ) + { + resultSize.height *= (maxWidth / resultSize.width); + resultSize.width = maxWidth; + } + if( resultSize.height > maxHeight ) + { + resultSize.width *= (maxHeight / resultSize.height); + resultSize.height = maxHeight; } - return YES; + return resultSize; +} + +// +// -[PictureController(Private) resizePanelForViewSize:animate:] +// +// Resizes the entire sheet to accomodate a view of a particular size. +// +- (void)resizeSheetForViewSize: (NSSize)viewSize +{ + // Figure out the deltas for the new frame area + NSSize currentSize = [fPictureViewArea frame].size; + CGFloat deltaX = viewSize.width - currentSize.width; + CGFloat deltaY = viewSize.height - currentSize.height; + + // Now resize the whole panel by those same deltas, but don't exceed the min + NSRect frame = [[self window] frame]; + NSSize maxSize = [[self window] maxSize]; + NSSize minSize = [[self window] minSize]; + frame.size.width += deltaX; + frame.size.height += deltaY; + if( frame.size.width < minSize.width ) + { + frame.size.width = minSize.width; + } + if( frame.size.height < minSize.height ) + { + frame.size.height = minSize.height; + } + + // But now the sheet is off-center, so also shift the origin to center it and + // keep the top aligned. + if( frame.size.width != [[self window] frame].size.width ) + frame.origin.x -= (deltaX / 2.0); + + if( frame.size.height != [[self window] frame].size.height ) + frame.origin.y -= deltaY; + + [[self window] setFrame:frame display:YES animate:YES]; +} + +// +// -[PictureController(Private) setViewSize:] +// +// Changes the view's size and centers it vertically inside of its area. +// Assumes resizeSheetForViewSize: has already been called. +// +- (void)setViewSize: (NSSize)viewSize +{ + [fPictureView setFrameSize:viewSize]; + + // center it vertically + NSPoint origin = [fPictureViewArea frame].origin; + origin.y += ([fPictureViewArea frame].size.height - + [fPictureView frame].size.height) / 2.0; + [fPictureView setFrameOrigin:origin]; +} + +// +// -[PictureController(Private) viewNeedsToResizeToSize:] +// +// Returns YES if the view will need to resize to match the given size. +// +- (BOOL)viewNeedsToResizeToSize: (NSSize)newSize +{ + NSSize viewSize = [fPictureView frame].size; + return (newSize.width != viewSize.width || newSize.height != viewSize.height); } @end