/* HBPicture.m $ This file is part of the HandBrake source code. Homepage: . It may be used under the terms of the GNU General Public License. */ #import "HBPicture.h" #import "HBTitle.h" #import "HBCodingUtilities.h" #import "HBMutablePreset.h" #import "NSDictionary+HBAdditions.h" #include "handbrake/handbrake.h" NSString * const HBPictureChangedNotification = @"HBPictureChangedNotification"; @interface HBPicture () @property (nonatomic, readwrite, getter=isValidating) BOOL validating; @property (nonatomic, readwrite, getter=areNotificationsEnabled) BOOL notificationsEnabled; @property (nonatomic, readonly) int modulus; @property (nonatomic, readwrite) int keep; @property (nonatomic, readwrite) BOOL darUpdated; @property (nonatomic, readonly) int sourceParNum; @property (nonatomic, readonly) int sourceParDen; @property (nonatomic, readonly) int autoCropTop; @property (nonatomic, readonly) int autoCropBottom; @property (nonatomic, readonly) int autoCropLeft; @property (nonatomic, readonly) int autoCropRight; @end @implementation HBPicture - (instancetype)init { self = [super init]; if (self) { // Set some values if we ever need a fake instance _maxWidth = 1920; _maxHeight = 1080; _width = 1920; _height = 1080; _sourceWidth = 1920; _sourceHeight = 1080; _anamorphicMode = HBPictureAnarmophicModeNone; _paddingMode = HBPicturePaddingModeNone; _paddingColorMode = HBPicturePaddingColorModeBlack; _paddingColorCustom = @""; _parWidth = 1; _parHeight = 1; _sourceParNum = 1; _sourceParDen = 1; } return self; } - (instancetype)initWithTitle:(HBTitle *)title { self = [self init]; if (self) { _width = title.width; _height = title.height; _sourceWidth = title.width; _sourceHeight = title.height; _sourceParNum = title.parWidth; _sourceParDen = title.parHeight; _autoCropTop = title.autoCropTop; _autoCropBottom = title.autoCropBottom; _autoCropLeft = title.autoCropLeft; _autoCropRight = title.autoCropRight; [self validateSettings]; _notificationsEnabled = YES; } return self; } - (void)postChangedNotification { if (self.areNotificationsEnabled) { [NSNotificationCenter.defaultCenter postNotification:[NSNotification notificationWithName:HBPictureChangedNotification object:self userInfo:nil]]; } } #pragma mark - Rotate - (void)setRotate:(int)rotate { if (rotate != _rotate) { [[self.undo prepareWithInvocationTarget:self] setRotate:_rotate]; } _rotate = rotate; [self postChangedNotification]; } #pragma mark - Flip - (void)setFlip:(BOOL)flip { if (flip != _flip) { [[self.undo prepareWithInvocationTarget:self] setFlip:_flip]; } _flip = flip; [self postChangedNotification]; } #pragma mark - Resolution limit - (void)setResolutionLimitMode:(HBPictureResolutionLimitMode)resolutionLimit { if (resolutionLimit != _resolutionLimitMode) { [[self.undo prepareWithInvocationTarget:self] setResolutionLimitMode:_resolutionLimitMode]; } _resolutionLimitMode = resolutionLimit; if (!(self.undo.isUndoing || self.undo.isRedoing)) { switch (resolutionLimit) { case HBPictureResolutionLimitModeNone: self.maxWidth = HB_MAX_WIDTH; self.maxHeight = HB_MAX_HEIGHT; break; case HBPictureResolutionLimitMode8K: self.maxWidth = 7680; self.maxHeight = 4320; break; case HBPictureResolutionLimitMode4K: self.maxWidth = 3840; self.maxHeight = 2160; break; case HBPictureResolutionLimitMode1080p: self.maxWidth = 1920; self.maxHeight = 1080; break; case HBPictureResolutionLimitMode720p: self.maxWidth = 1280; self.maxHeight = 720; break; case HBPictureResolutionLimitMode576p: self.maxWidth = 720; self.maxHeight = 576; break; case HBPictureResolutionLimitMode480p: self.maxWidth = 720; self.maxHeight = 480; break; case HBPictureResolutionLimitModeCustom: self.maxWidth = self.sourceWidth; self.maxHeight = self.sourceHeight; break; } } if (!self.isValidating) { [self validateSettings]; } } - (void)setMaxWidth:(int)maxWidth { if (maxWidth != _maxWidth) { [[self.undo prepareWithInvocationTarget:self] setMaxWidth:_maxWidth]; } _maxWidth = maxWidth; if (!self.isValidating) { [self validateSettings]; } } - (void)setMaxHeight:(int)maxHeight { if (maxHeight != _maxHeight) { [[self.undo prepareWithInvocationTarget:self] setMaxHeight:_maxHeight]; } _maxHeight = maxHeight; if (!self.isValidating) { [self validateSettings]; } } - (void)setAllowUpscaling:(BOOL)allowUpscaling { if (allowUpscaling != _allowUpscaling) { [[self.undo prepareWithInvocationTarget:self] setAllowUpscaling:_allowUpscaling]; } _allowUpscaling = allowUpscaling; if (!self.isValidating) { [self validateSettings]; } } - (void)setUseMaximumSize:(BOOL)useMaximumSize { if (useMaximumSize != _useMaximumSize) { [[self.undo prepareWithInvocationTarget:self] setUseMaximumSize:_useMaximumSize]; } _useMaximumSize = useMaximumSize; if (!self.isValidating) { [self validateSettings]; } } #pragma mark - Size - (void)setWidth:(int)width { if (width != _width) { [[self.undo prepareWithInvocationTarget:self] setWidth:_width]; } _width = width; if (!self.isValidating) { self.keep |= HB_KEEP_WIDTH; [self validateSettings]; } } - (BOOL)validateWidth:(id *)ioValue error:(NSError * __autoreleasing *)outError { BOOL retval = YES; if (nil != *ioValue) { int value = [*ioValue intValue]; int roundedValue = value - (value % self.modulus); if (value >= self.maxWidth) { *ioValue = @(self.maxWidth); } else if (value <= HB_MIN_WIDTH) { *ioValue = @(HB_MIN_WIDTH); } else if (value != roundedValue) { *ioValue = @(roundedValue); } } return retval; } - (void)setHeight:(int)height { if (height != _height) { [[self.undo prepareWithInvocationTarget:self] setHeight:_height]; } _height = height; if (!self.isValidating) { self.keep |= HB_KEEP_HEIGHT; [self validateSettings]; } } - (BOOL)validateHeight:(id *)ioValue error:(NSError * __autoreleasing *)outError { BOOL retval = YES; if (nil != *ioValue) { int value = [*ioValue intValue]; int roundedValue = value - (value % self.modulus); if (value >= self.maxHeight) { *ioValue = @(self.maxHeight); } else if (value <= HB_MIN_HEIGHT) { *ioValue = @(HB_MIN_HEIGHT); } else if (value != roundedValue) { *ioValue = @(roundedValue); } } return retval; } - (void)setDisplayWidth:(int)displayWidth { if (displayWidth != _displayWidth) { [[self.undo prepareWithInvocationTarget:self] setDisplayWidth:_displayWidth]; } _displayWidth = displayWidth; if (!self.isValidating) { self.darUpdated = YES; [self validateSettings]; } } - (void)setParWidth:(int)parWidth { if (parWidth != _parWidth) { [[self.undo prepareWithInvocationTarget:self] setParWidth:_parWidth]; } _parWidth = parWidth; if (!self.isValidating) { [self validateSettings]; } } - (void)setParHeight:(int)parHeight { if (parHeight != _parHeight) { [[self.undo prepareWithInvocationTarget:self] setParHeight:_parHeight]; } _parHeight = parHeight; if (!self.isValidating) { [self validateSettings]; } } #pragma mark - Crop - (void)setCropTop:(int)cropTop { if (cropTop != _cropTop) { [[self.undo prepareWithInvocationTarget:self] setCropTop:_cropTop]; } _cropTop = cropTop; if (!self.isValidating) { [self validateSettings]; } } - (void)setCropBottom:(int)cropBottom { if (cropBottom != _cropBottom) { [[self.undo prepareWithInvocationTarget:self] setCropBottom:_cropBottom]; } _cropBottom = cropBottom; if (!self.isValidating) { [self validateSettings]; } } - (void)setCropLeft:(int)cropLeft { if (cropLeft != _cropLeft) { [[self.undo prepareWithInvocationTarget:self] setCropLeft:_cropLeft]; } _cropLeft = cropLeft; if (!self.isValidating) { [self validateSettings]; } } - (void)setCropRight:(int)cropRight { if (cropRight != _cropRight) { [[self.undo prepareWithInvocationTarget:self] setCropRight:_cropRight]; } _cropRight = cropRight; if (!self.isValidating) { [self validateSettings]; } } - (BOOL)validateCropTop:(id *)ioValue error:(NSError * __autoreleasing *)outError { [self validateCrop:ioValue max:self.maxTopCrop]; return YES; } - (BOOL)validateCropBottom:(id *)ioValue error:(NSError * __autoreleasing *)outError { [self validateCrop:ioValue max:self.maxBottomCrop]; return YES; } - (BOOL)validateCropLeft:(id *)ioValue error:(NSError * __autoreleasing *)outError { [self validateCrop:ioValue max:self.maxLeftCrop]; return YES; } - (BOOL)validateCropRight:(id *)ioValue error:(NSError * __autoreleasing *)outError { [self validateCrop:ioValue max:self.maxRightCrop]; return YES; } - (void)validateCrop:(NSNumber **)ioValue max:(int)maxCrop { if (nil != *ioValue) { int value = [*ioValue intValue]; if (value >= maxCrop) { *ioValue = @(maxCrop); } else if (value < 0) { *ioValue = @0; } } } - (void)setAutocrop:(BOOL)autocrop { if (autocrop != _autocrop) { [[self.undo prepareWithInvocationTarget:self] setAutocrop:_autocrop]; } _autocrop = autocrop; if (autocrop && !self.isValidating) { if (!(self.undo.isUndoing || self.undo.isRedoing)) { self.validating = YES; // Reset the crop values to those determined right after scan self.cropTop = self.autoCropTop; self.cropBottom = self.autoCropBottom; self.cropLeft = self.autoCropLeft; self.cropRight = self.autoCropRight; self.validating = NO; } [self validateSettings]; } } #pragma mark - Padding - (void)setPaddingTop:(int)paddingTop { if (paddingTop != _paddingTop) { [[self.undo prepareWithInvocationTarget:self] setPaddingTop:_paddingTop]; } _paddingTop = paddingTop; if (!self.isValidating) { [self validateSettings]; } } - (void)setPaddingBottom:(int)paddingBottom { if (paddingBottom != _paddingBottom) { [[self.undo prepareWithInvocationTarget:self] setPaddingBottom:_paddingBottom]; } _paddingBottom = paddingBottom; if (!self.isValidating) { [self validateSettings]; } } - (void)setPaddingLeft:(int)paddingLeft { if (paddingLeft != _paddingLeft) { [[self.undo prepareWithInvocationTarget:self] setPaddingLeft:_paddingLeft]; } _paddingLeft = paddingLeft; if (!self.isValidating) { [self validateSettings]; } } - (void)setPaddingRight:(int)paddingRight { if (paddingRight != _paddingRight) { [[self.undo prepareWithInvocationTarget:self] setPaddingRight:_paddingRight]; } _paddingRight = paddingRight; if (!self.isValidating) { [self validateSettings]; } } - (BOOL)validatePaddingTop:(id *)ioValue error:(NSError * __autoreleasing *)outError { [self validatePadding:ioValue max:self.maxTopPadding]; return YES; } - (BOOL)validatePaddingBottom:(id *)ioValue error:(NSError * __autoreleasing *)outError { [self validatePadding:ioValue max:self.maxBottomPadding]; return YES; } - (BOOL)validatePaddingLeft:(id *)ioValue error:(NSError * __autoreleasing *)outError { [self validatePadding:ioValue max:self.maxLeftPadding]; return YES; } - (BOOL)validatePaddingRight:(id *)ioValue error:(NSError * __autoreleasing *)outError { [self validatePadding:ioValue max:self.maxRightPadding]; return YES; } - (void)validatePadding:(NSNumber **)ioValue max:(int)maxPadding { if (nil != *ioValue) { int value = [*ioValue intValue]; if (value >= maxPadding) { *ioValue = @(maxPadding); } else if (value < 0) { *ioValue = @0; } } } - (void)setPaddingMode:(HBPicturePaddingMode)paddingMode { if (paddingMode != _paddingMode) { [[self.undo prepareWithInvocationTarget:self] setPaddingMode:_paddingMode]; } _paddingMode = paddingMode; if (!self.isValidating) { [self validateSettings]; } } - (void)setPaddingColorMode:(HBPicturePaddingColorMode)paddingColorMode { if (paddingColorMode != _paddingColorMode) { [[self.undo prepareWithInvocationTarget:self] setPaddingColorMode:_paddingColorMode]; } _paddingColorMode = paddingColorMode; if (!self.isValidating) { [self validateSettings]; } } #pragma mark - Anamorphic - (void)setAnamorphicMode:(HBPictureAnarmophicMode)anamorphicMode { if (anamorphicMode != _anamorphicMode) { [[self.undo prepareWithInvocationTarget:self] setAnamorphicMode:_anamorphicMode]; } _anamorphicMode = anamorphicMode; if (self.anamorphicMode == HB_ANAMORPHIC_AUTO || self.anamorphicMode == HB_ANAMORPHIC_LOOSE) { self.keepDisplayAspect = YES; } if (!self.isValidating) { [self validateSettings]; } } - (void)setKeepDisplayAspect:(BOOL)keepDisplayAspect { if (keepDisplayAspect != _keepDisplayAspect) { [[self.undo prepareWithInvocationTarget:self] setKeepDisplayAspect:_keepDisplayAspect]; } _keepDisplayAspect = keepDisplayAspect; if (!self.isValidating) { [self validateSettings]; } } - (int)modulus { return 2; } #pragma mark - Max sizes - (int)maxTopCrop { return self.sourceHeight - self.cropBottom - HB_MIN_HEIGHT; } + (NSSet *)keyPathsForValuesAffectingMaxBottomCrop { return [NSSet setWithObjects:@"cropTop", nil]; } - (int)maxBottomCrop { return self.sourceHeight - self.cropTop - HB_MIN_HEIGHT; } + (NSSet *)keyPathsForValuesAffectingMaxLeftCrop { return [NSSet setWithObjects:@"cropRight", nil]; } - (int)maxLeftCrop { return self.sourceWidth - self.cropRight - HB_MIN_WIDTH; } + (NSSet *)keyPathsForValuesAffectingMaxRightCrop { return [NSSet setWithObjects:@"cropLeft", nil]; } - (int)maxRightCrop { return self.sourceWidth - self.cropLeft - HB_MIN_WIDTH; } + (NSSet *)keyPathsForValuesAffectingMaxTopPadding { return [NSSet setWithObjects:@"paddingBottom", nil]; } - (int)maxTopPadding { return self.maxHeight - self.height - self.paddingBottom; } + (NSSet *)keyPathsForValuesAffectingMaxBottomPadding { return [NSSet setWithObjects:@"paddingTop", nil]; } - (int)maxBottomPadding { return self.maxHeight - self.height - self.paddingTop; } + (NSSet *)keyPathsForValuesAffectingMaxLeftPadding { return [NSSet setWithObjects:@"paddingRight", nil]; } - (int)maxLeftPadding { return self.maxWidth - self.width - self.paddingRight; } + (NSSet *)keyPathsForValuesAffectingMaxRightPadding { return [NSSet setWithObjects:@"paddingLeft", nil]; } - (int)maxRightPadding { return self.maxWidth - self.width - self.maxLeftPadding; } - (int)sourceDisplayWidth { return (int) (self.sourceWidth * self.sourceParNum / (double)self.sourceParDen); } - (void)setNilValueForKey:(NSString *)key { if ([key isEqualToString:@"width"] || [key isEqualToString:@"height"]) { [self setValue:@64 forKey:key]; } else { [self setValue:@0 forKey:key]; } } #pragma mark - Picture Update Logic /** * Validates the settings through hb_set_anamorphic_size2, * each setters calls this after setting its value. */ - (void)validateSettings { self.validating = YES; if (!(self.undo.isUndoing || self.undo.isRedoing)) { self.keep |= self.keepDisplayAspect * HB_KEEP_DISPLAY_ASPECT; hb_geometry_t srcGeo, resultGeo; hb_geometry_settings_t uiGeo; srcGeo.width = self.sourceWidth; srcGeo.height = self.sourceHeight; srcGeo.par.num = self.sourceParNum; srcGeo.par.den = self.sourceParDen; uiGeo.mode = (int)self.anamorphicMode; uiGeo.keep = self.keep; uiGeo.itu_par = 0; uiGeo.modulus = self.modulus; int crop[4] = {self.cropTop, self.cropBottom, self.cropLeft, self.cropRight}; memcpy(uiGeo.crop, crop, sizeof(int[4])); uiGeo.geometry.width = self.useMaximumSize || self.maxWidth < self.width ? self.maxWidth : self.width; uiGeo.geometry.height = self.useMaximumSize || self.maxHeight < self.height ? self.maxHeight : self.height; // Modulus added to maxWidth/maxHeight to allow a small amount of // upscaling to the next mod boundary. uiGeo.maxWidth = self.allowUpscaling || self.maxWidth < self.width ? self.maxWidth : self.sourceWidth - crop[2] - crop[3] + self.modulus - 1; uiGeo.maxHeight = self.allowUpscaling || self.maxHeight < self.height ? self.maxHeight : self.sourceHeight - crop[0] - crop[1] + self.modulus - 1; hb_rational_t par = {self.parWidth, self.parHeight}; uiGeo.geometry.par = par; if (self.anamorphicMode == HB_ANAMORPHIC_CUSTOM && self.darUpdated) { uiGeo.geometry.par.num = self.displayWidth; uiGeo.geometry.par.den = uiGeo.geometry.width; } hb_set_anamorphic_size2(&srcGeo, &uiGeo, &resultGeo); int display_width; display_width = resultGeo.width * resultGeo.par.num / resultGeo.par.den; self.width = resultGeo.width; self.height = resultGeo.height; self.parWidth = resultGeo.par.num; self.parHeight = resultGeo.par.den; self.displayWidth = display_width; int maxPaddingX = self.maxWidth - self.width; int maxPaddingY = self.maxHeight - self.height; switch (self.paddingMode) { case HBPicturePaddingModeNone: self.paddingRight = 0; self.paddingLeft = 0; self.paddingTop = 0; self.paddingBottom = 0; break; case HBPicturePaddingModeFill: self.paddingRight = maxPaddingX / 2; self.paddingLeft = maxPaddingX / 2; self.paddingTop = maxPaddingY / 2; self.paddingBottom = maxPaddingY / 2; break; case HBPicturePaddingModeFillHeight: self.paddingRight = 0; self.paddingLeft = 0; self.paddingTop = maxPaddingY / 2; self.paddingBottom = maxPaddingY / 2; break; case HBPicturePaddingModeFillWidth: self.paddingRight = maxPaddingX / 2; self.paddingLeft = maxPaddingX / 2; self.paddingTop = 0; self.paddingBottom = 0; break; case HBPicturePaddingModeCustom: break; } } self.validating = NO; self.keep = 0; self.darUpdated = NO; [self postChangedNotification]; } #pragma mark - NSCopying - (instancetype)copyWithZone:(NSZone *)zone { HBPicture *copy = [[[self class] alloc] init]; if (copy) { copy->_rotate = _rotate; copy->_flip = _flip; copy->_resolutionLimitMode = _resolutionLimitMode; copy->_maxWidth = _maxWidth; copy->_maxHeight = _maxHeight; copy->_allowUpscaling = _allowUpscaling; copy->_useMaximumSize = _useMaximumSize; copy->_width = _width; copy->_height = _height; copy->_keepDisplayAspect = _keepDisplayAspect; copy->_anamorphicMode = _anamorphicMode; copy->_displayWidth = _displayWidth; copy->_parWidth = _parWidth; copy->_parHeight = _parHeight; copy->_autocrop = _autocrop; copy->_cropTop = _cropTop; copy->_cropBottom = _cropBottom; copy->_cropLeft = _cropLeft; copy->_cropRight = _cropRight; copy->_autoCropTop = _autoCropTop; copy->_autoCropBottom = _autoCropBottom; copy->_autoCropLeft = _autoCropLeft; copy->_autoCropRight = _autoCropRight; copy->_paddingMode = _paddingMode; copy->_paddingTop = _paddingTop; copy->_paddingBottom = _paddingBottom; copy->_paddingLeft = _paddingLeft; copy->_paddingRight = _paddingRight; copy->_paddingColorMode = _paddingColorMode; copy->_paddingColorCustom = [_paddingColorCustom copy]; copy->_sourceWidth = _sourceWidth; copy->_sourceHeight = _sourceHeight; copy->_sourceParNum = _sourceParNum; copy->_sourceParDen = _sourceParDen; copy->_notificationsEnabled = _notificationsEnabled; } return copy; } #pragma mark - NSCoding + (BOOL)supportsSecureCoding { return YES; } - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeInt:2 forKey:@"HBPictureVersion"]; encodeInt(_rotate); encodeBool(_flip); encodeInteger(_resolutionLimitMode); encodeInt(_maxWidth); encodeInt(_maxHeight); encodeBool(_allowUpscaling); encodeBool(_useMaximumSize); encodeInt(_width); encodeInt(_height); encodeBool(_keepDisplayAspect); encodeInteger(_anamorphicMode); encodeInt(_displayWidth); encodeInt(_parWidth); encodeInt(_parHeight); encodeBool(_autocrop); encodeInt(_cropTop); encodeInt(_cropBottom); encodeInt(_cropLeft); encodeInt(_cropRight); encodeInt(_autoCropTop); encodeInt(_autoCropBottom); encodeInt(_autoCropLeft); encodeInt(_autoCropRight); encodeInteger(_paddingMode); encodeInt(_paddingTop); encodeInt(_paddingBottom); encodeInt(_paddingLeft); encodeInt(_paddingRight); encodeInteger(_paddingColorMode); encodeObject(_paddingColorCustom); encodeInt(_sourceWidth); encodeInt(_sourceHeight); encodeInt(_sourceParNum); encodeInt(_sourceParDen); } - (instancetype)initWithCoder:(NSCoder *)decoder { self = [super init]; decodeInt(_rotate); if (_rotate != 0 && _rotate != 90 && _rotate != 180 && _rotate != 270) { goto fail; } decodeBool(_flip); decodeInteger(_resolutionLimitMode); if (_resolutionLimitMode < HBPictureResolutionLimitModeNone || _resolutionLimitMode > HBPictureResolutionLimitModeCustom) { goto fail; } decodeInt(_maxWidth); if (_maxWidth < 0) { goto fail; } decodeInt(_maxHeight); if (_maxHeight < 0) { goto fail; } decodeBool(_allowUpscaling); decodeBool(_useMaximumSize); decodeInt(_width); if (_width < 0) { goto fail; } decodeInt(_height); if (_height < 0) { goto fail; } decodeBool(_keepDisplayAspect); decodeInteger(_anamorphicMode); if (_anamorphicMode < HBPictureAnarmophicModeNone || _anamorphicMode > HBPictureAnarmophicModeAuto) { goto fail; } decodeInt(_displayWidth); if (_displayWidth < 0) { goto fail; } decodeInt(_parWidth); if (_parWidth < 0) { goto fail; } decodeInt(_parHeight); if (_parHeight < 0) { goto fail; } decodeBool(_autocrop); decodeInt(_cropTop); if (_cropTop < 0) { goto fail; } decodeInt(_cropBottom); if (_cropBottom < 0) { goto fail; } decodeInt(_cropLeft); if (_cropLeft < 0) { goto fail; } decodeInt(_cropRight); if (_cropRight < 0) { goto fail; } decodeInt(_autoCropTop); if (_autoCropTop < 0) { goto fail; } decodeInt(_autoCropBottom); if (_autoCropBottom < 0) { goto fail; } decodeInt(_autoCropLeft); if (_autoCropLeft < 0) { goto fail; } decodeInt(_autoCropRight); if (_autoCropRight < 0) { goto fail; } decodeInteger(_paddingMode); if (_paddingMode < HBPicturePaddingModeNone || _paddingMode > HBPicturePaddingModeCustom) { goto fail; } decodeInt(_paddingTop); if (_paddingTop < 0) { goto fail; } decodeInt(_paddingBottom); if (_paddingBottom < 0) { goto fail; } decodeInt(_paddingLeft); if (_paddingLeft < 0) { goto fail; } decodeInt(_paddingRight); if (_paddingRight < 0) { goto fail; } decodeInteger(_paddingColorMode); if (_paddingColorMode < HBPicturePaddingColorModeBlack || _paddingColorMode > HBPicturePaddingColorModeCustom) { goto fail; } decodeObject(_paddingColorCustom, NSString); decodeInt(_sourceWidth); if (_sourceWidth < 0) { goto fail; } decodeInt(_sourceHeight); if (_sourceHeight < 0) { goto fail; } decodeInt(_sourceParNum); if (_sourceParNum < 0) { goto fail; } decodeInt(_sourceParDen); if (_sourceParDen < 0) { goto fail; } _notificationsEnabled = YES; return self; fail: return nil; } #pragma mark - Presets - (void)writeToPreset:(HBMutablePreset *)preset { preset[@"PictureRotate"] = [NSString stringWithFormat:@"angle=%d:hflip=%d", self.rotate, self.flip]; preset[@"PictureWidth"] = @(self.maxWidth); preset[@"PictureHeight"] = @(self.maxHeight); preset[@"PictureAllowUpscaling"] = @(self.allowUpscaling); preset[@"PictureUseMaximumSize"] = @(self.useMaximumSize); preset[@"PictureKeepRatio"] = @(self.keepDisplayAspect); switch (self.anamorphicMode) { case HB_ANAMORPHIC_NONE: preset[@"PicturePAR"] = @"off"; break; case HB_ANAMORPHIC_LOOSE: preset[@"PicturePAR"] = @"loose"; break; case HB_ANAMORPHIC_AUTO: preset[@"PicturePAR"] = @"auto"; break; case HB_ANAMORPHIC_CUSTOM: preset[@"PicturePAR"] = @"custom"; break; default: preset[@"PicturePAR"] = @"loose"; break; } // PAR preset[@"PicturePARWidth"] = @(self.parWidth); preset[@"PicturePARHeight"] = @(self.parHeight); // Set crop settings preset[@"PictureAutoCrop"] = @(self.autocrop); preset[@"PictureTopCrop"] = @(self.cropTop); preset[@"PictureBottomCrop"] = @(self.cropBottom); preset[@"PictureLeftCrop"] = @(self.cropLeft); preset[@"PictureRightCrop"] = @(self.cropRight); // Padding preset[@"PicturePadMode"] = @(self.paddingMode); int width = self.width + self.paddingLeft + self.paddingRight; int height = self.height + self.paddingTop + self.paddingBottom; NSString *color = self.paddingColorCustom; preset[@"PicturePad"] = [NSString stringWithFormat:@"width=%d:height=%d:color=%@:x=%d:y=%d", width, height, color, self.paddingLeft, self.paddingTop]; } - (void)applyPreset:(HBPreset *)preset jobSettings:(NSDictionary *)settings { NSDictionary *par = settings[@"PAR"]; NSDictionary *filterList = settings[@"Filters"][@"FilterList"]; NSDictionary *cropScale = nil; for (NSDictionary *dict in filterList) { if ([dict[@"ID"] intValue] == HB_FILTER_CROP_SCALE) { cropScale = dict[@"Settings"]; } } self.validating = YES; self.notificationsEnabled = NO; // Rotate NSString *rotate = preset[@"PictureRotate"]; hb_dict_t *hbdict = hb_parse_filter_settings(rotate.UTF8String); NSDictionary *dict = [[NSDictionary alloc] initWithHBDict:hbdict]; hb_value_free(&hbdict); self.rotate = [dict[@"angle"] intValue]; self.flip = [dict[@"hflip"] boolValue]; self.maxWidth = [preset[@"PictureWidth"] intValue]; self.maxHeight = [preset[@"PictureHeight"] intValue]; self.allowUpscaling = [preset[@"PictureAllowUpscaling"] boolValue]; self.useMaximumSize = [preset[@"PictureUseMaximumSize"] boolValue]; if (self.maxWidth == 0 && self.maxHeight == 0) { self.resolutionLimitMode = HBPictureResolutionLimitModeNone; } else if (self.maxWidth == 7680 && self.maxHeight == 4320) { self.resolutionLimitMode = HBPictureResolutionLimitMode8K; } else if (self.maxWidth == 3840 && self.maxHeight == 2160) { self.resolutionLimitMode = HBPictureResolutionLimitMode4K; } else if (self.maxWidth == 1920 && self.maxHeight == 1080) { self.resolutionLimitMode = HBPictureResolutionLimitMode1080p; } else if (self.maxWidth == 1280 && self.maxHeight == 720) { self.resolutionLimitMode = HBPictureResolutionLimitMode720p; } else if (self.maxWidth == 720 && self.maxHeight == 576) { self.resolutionLimitMode = HBPictureResolutionLimitMode576p; } else if (self.maxWidth == 720 && self.maxHeight == 480) { self.resolutionLimitMode = HBPictureResolutionLimitMode480p; } else { self.resolutionLimitMode = HBPictureResolutionLimitModeCustom; } if (cropScale) { // If Cropping is set to custom, then recall all four crop values from // when the preset was created and apply them if ([preset[@"PictureAutoCrop"] boolValue]) { self.autocrop = YES; // Here we use the auto crop values determined right after scan self.cropTop = [cropScale[@"crop-top"] intValue]; self.cropBottom = [cropScale[@"crop-bottom"] intValue]; self.cropLeft = [cropScale[@"crop-left"] intValue]; self.cropRight = [cropScale[@"crop-right"] intValue]; } else { self.autocrop = NO; // Here we use the custom crop values saved at the time the preset was saved self.cropTop = [preset[@"PictureTopCrop"] intValue]; self.cropBottom = [preset[@"PictureBottomCrop"] intValue]; self.cropLeft = [preset[@"PictureLeftCrop"] intValue]; self.cropRight = [preset[@"PictureRightCrop"] intValue]; } // Assume max picture settings initially. self.keepDisplayAspect = [preset[@"PictureKeepRatio"] boolValue]; if ([preset[@"PicturePAR"] isEqualToString:@"off"]) { self.anamorphicMode = (HBPictureAnarmophicMode)HB_ANAMORPHIC_NONE; } else if ([preset[@"PicturePAR"] isEqualToString:@"auto"]) { self.anamorphicMode = (HBPictureAnarmophicMode)HB_ANAMORPHIC_AUTO; } else if ([preset[@"PicturePAR"] isEqualToString:@"custom"]) { self.anamorphicMode = (HBPictureAnarmophicMode)HB_ANAMORPHIC_CUSTOM; } else { self.anamorphicMode = (HBPictureAnarmophicMode)HB_ANAMORPHIC_LOOSE; } self.parWidth = [par[@"Num"] intValue]; self.parHeight = [par[@"Den"] intValue]; self.width = [cropScale[@"width"] intValue]; self.height = [cropScale[@"height"] intValue]; self.displayWidth = self.width * self.parWidth / self.parHeight; } // Padding NSString *pad = preset[@"PicturePad"]; hb_dict_t *hbPadDict = hb_parse_filter_settings(pad.UTF8String); NSDictionary *padDict = [[NSDictionary alloc] initWithHBDict:hbPadDict]; hb_value_free(&hbPadDict); self.paddingMode = [preset[@"PicturePadMode"] intValue]; if (self.paddingMode == HBPicturePaddingModeCustom) { self.paddingLeft = [padDict[@"x"] intValue]; self.paddingRight = [padDict[@"width"] intValue] - self.width - self.paddingLeft; self.paddingTop = [padDict[@"y"] intValue]; self.paddingBottom = [padDict[@"height"] intValue] - self.height - self.paddingTop; } else { self.paddingLeft = 0; self.paddingRight = 0; self.paddingTop = 0; self.paddingBottom = 0; } self.validating = NO; self.notificationsEnabled = YES; [self postChangedNotification]; } @end