26、Cocoa 开发中图像、阴影与渐变的使用指南

Cocoa 开发中图像、阴影与渐变的使用指南

在 Cocoa 开发中,图像的加载、绘制,以及阴影和渐变效果的应用是非常重要的部分。下面将详细介绍这些内容的使用方法和相关技巧。

1. 图像加载

当图像被添加到项目中后,可以使用 NSImage 类的 +imageNamed: 方法自动加载它。例如,若文件名为 SpaceShuttle.jpg ,可以使用以下代码将其加载为 NSImage 对象:

NSImage* mainImage = [NSImage imageNamed:@"SpaceShuttle"];

为了避免每次绘制时都重新创建图像,影响性能,建议在应用启动时只加载一次图像。

加载标准 Cocoa 图标

同样使用 +imageNamed: 方法加载标准图标,但需要使用 AppKit NSImage.h 头文件中声明的常量,而不是文件名。以下是一些常见的常量示例:

NSString *const NSImageNameComputer;
NSString *const NSImageNameColorPanel;
NSString *const NSImageNameUser;
NSString *const NSImageNameNetwork;

例如,创建一个代表计算机的图像:

NSImage* computerImage = [NSImage imageNamed:NSImageNameComputer];

需要注意的是,不同版本的 Mac OS X 中,同一图像名称对应的实际图标可能不同,但图标含义保持一致。同时,Cocoa 的标准图标名称是全局变量,不要加引号。完整的标准图标名称列表位于 NSImage.h 文件底部,也可以在 Interface Builder 的 Library 面板的 Media 标签中查看部分名称。

2. 在视图中绘制图像

NSImage 类虽然复杂,但基本的绘制操作很简单。常用的绘制方法有两个:

- (void)drawAtPoint: (NSPoint)point
           fromRect: (NSRect)fromRect
          operation: (NSCompositingOperation)op
           fraction: (CGFloat)delta;

- (void)drawInRect: (NSRect)rect
          fromRect: (NSRect)fromRect
         operation: (NSCompositingOperation)op
          fraction: (CGFloat)delta;

这两个方法的区别在于,第一个方法以 NSPoint 作为第一个输入参数,第二个方法以 NSRect 作为第一个输入参数。如果指定 rect ,绘制的图像将被缩放以填充整个矩形区域;如果指定 point ,将使用图像的自然大小(但不超过 fromRect 的大小)。可以使用 fromRect 绘制源图像的部分区域,也可以传入 NSZeroRect 绘制整个图像。 operation 参数类似于图像编辑软件中的混合模式,这里使用 NSCompositeSourceOver fraction 参数用于设置图像的透明度,例如 0.5 表示 50% 不透明度。

创建自定义图像视图

在 Xcode 的 BasicCocoaDrawing 项目中,添加一个新的 NSView StyledImageView.m ,并确保勾选 “Also create StyledImageView.h” 和 BasicCocoaDrawing 目标。在 StyledImageView.h 中添加以下属性:

#import <Cocoa/Cocoa.h>
@interface StyledImageView : NSView
@property (retain) NSImage* mainImage;
@property (retain) NSColor* backgroundColor;
@property (retain) NSColor* borderColor;
@end

StyledImageView.m 中实现相应的方法:

#import "StyledImageView.h"
@implementation StyledImageView
    @synthesize mainImage;
    @synthesize backgroundColor;
    @synthesize borderColor;

- (id)initWithFrame:(NSRect)frame {
    if ( self = [super initWithFrame:frame] ) {
    }
    return self;
}

- (void)drawRect:(NSRect)rect {
    NSRect bounds = self.bounds;
    // 绘制背景颜色
    [self.backgroundColor set]; 
    NSRectFill ( bounds );

    CGFloat insetX      = NSWidth     ( bounds ) * 0.10;
    CGFloat insetY      = NSHeight    ( bounds ) * 0.10;    
    NSRect  imageRect   = NSInsetRect ( bounds, insetX, insetY );

    // 绘制图像
    NSImage* image = self.mainImage;
    [image drawInRect: imageRect
             fromRect: NSZeroRect
            operation: NSCompositeSourceOver
             fraction: 1.0];

    // 绘制图像边框
    [self.borderColor set];
    NSBezierPath* imageFrame = [NSBezierPath bezierPathWithRect:imageRect];
    imageFrame.lineWidth = 4;
    [imageFrame stroke];
}

- (void) dealloc {
    self.mainImage       = nil;
    self.backgroundColor = nil;
    self.borderColor     = nil;
    [super dealloc];
}
@end

BasicCocoaDrawingAppDelegate.m 中修改 applicationDidFinishLaunching: 方法:

#import "BasicCocoaDrawingAppDelegate.h"
#import "StyledImageView.h"
@implementation BasicCocoaDrawingAppDelegate
@synthesize window;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    NSRect viewFrame = [self.window.contentView bounds];
    StyledImageView* imageView;
    imageView = [[StyledImageView alloc] initWithFrame:viewFrame];
    // 设置视图属性
    imageView.mainImage       = [NSImage imageNamed:@"SpaceShuttle"];
    imageView.backgroundColor = [NSColor darkGrayColor];
    imageView.borderColor     = [NSColor whiteColor];

    // 随窗口调整大小
    NSInteger resizingMask = ( NSViewWidthSizable | NSViewHeightSizable );
    [imageView setAutoresizingMask:resizingMask];
    // 添加到内容视图并释放
    [self.window.contentView addSubview:imageView];
    [imageView release];
}
@end

保存所有文件并重新运行应用,图像将显示在视图中心,并带有边框。

3. 保持图像宽高比

在调整窗口大小时,图像可能会被拉伸变形。为了解决这个问题,可以为 NSImage 创建一个类别。在 Xcode 中创建一个新的 Objective-C 类文件 NSImage-Utilities.m 及其头文件,并修改 NSImage-Utilities.h 如下:

#import <Cocoa/Cocoa.h>
@interface NSImage (Utilities)
- (NSRect) proportionalRectForTargetRect:(NSRect)targetRect;
@end

NSImage-Utilities.m 中实现该方法:

#import "NSImage-Utilities.h"
@implementation NSImage (Utilities)
- (NSRect) proportionalRectForTargetRect:(NSRect)targetRect {
    // 如果大小相同,直接返回目标矩形
    if ( NSEqualSizes(self.size, targetRect.size) ) return targetRect;

    NSSize  imageSize    = self.size;
    CGFloat soureWidth   = imageSize.width;
    CGFloat sourceHeight = imageSize.height;

    // 计算宽高调整比例,取较大值
    CGFloat widthAdjust  = targetRect.size.width  / soureWidth;
    CGFloat heightAdjust = targetRect.size.height / sourceHeight;
    CGFloat scaleFactor  = 1.0;

    if ( widthAdjust < heightAdjust )
        scaleFactor = widthAdjust;
    else
        scaleFactor = heightAdjust;

    // 按相同比例调整宽高
    CGFloat finalWidth  = soureWidth   * scaleFactor;
    CGFloat finalHeight = sourceHeight * scaleFactor;
    NSSize  finalSize   = NSMakeSize ( finalWidth, finalHeight );

    // 计算最终矩形
    NSRect finalRect;
    finalRect.size = finalSize;
    finalRect.origin    = targetRect.origin;
    finalRect.origin.x += (targetRect.size.width  - finalWidth)  * 0.5;
    finalRect.origin.y += (targetRect.size.height - finalHeight) * 0.5;

    // 返回精确坐标
    return NSIntegralRect ( finalRect );
}
@end

StyledImageView.m 中引入该类别,并调用方法:

#import "StyledImageView.h"
#import "NSImage-Utilities.h"

// ...

- (void)drawRect:(NSRect)rect {
    // ...
    NSImage* image = self.mainImage;
    imageRect = [image proportionalRectForTargetRect:imageRect];
    // ...
}

重新运行应用,图像将保持正确的宽高比。

4. 添加阴影效果

NSShadow 类可以为形状或文本添加阴影。使用阴影的方式与使用颜色类似,创建阴影对象并调用 -set 方法应用到当前图形上下文。以下是一个简单的示例:

NSShadow* dropShadow = [[NSShadow alloc] init];    
dropShadow.shadowBlurRadius  = 8.0;
dropShadow.shadowOffset      = NSMakeSize(0,-6);
dropShadow.shadowColor       = [NSColor blackColor];
[dropShadow set];
NSRect fillRect = NSMakeRect ( 20, 20, 100, 100 );
[[NSColor whiteColor] set];
[NSBezierPath fillRect:fillRect];
[dropShadow release];

若将 shadowColor 属性设置为 [NSColor whiteColor] ,可以创建发光效果。需要注意的是,没有直接取消阴影的方法,建议在设置阴影前保存图形上下文,使用完后恢复之前的上下文。

StyledImageView 添加阴影

StyledImageView.h 中添加一个新属性来控制是否添加阴影:

#import <Cocoa/Cocoa.h>
@interface StyledImageView : NSView
@property (retain) NSImage* mainImage;
@property (retain) NSColor* backgroundColor;
@property (retain) NSColor* borderColor;
@property (assign) BOOL     shouldAddShadow;
@end

StyledImageView.m 中添加私有属性和方法:

#import "StyledImageView.h"
#import "NSImage-Utilities.h"
@interface StyledImageView ()
// 私有属性
@property (retain) NSShadow* imageShadow;
// 私有方法
+ (NSShadow*) defaultImageShadow;
@end

@implementation StyledImageView
@synthesize mainImage;
@synthesize backgroundColor;
@synthesize borderColor;
@synthesize shouldAddShadow;
@synthesize imageShadow;

- (void) setShouldAddShadow:(BOOL)shouldAdd {
    // 如果新值相同,直接返回
    if ( shouldAddShadow == shouldAdd ) return;

    // 设置新值
    shouldAddShadow = shouldAdd;

    if ( shouldAddShadow )
        self.imageShadow = [[self class] defaultImageShadow];   
    else
        self.imageShadow = nil;

    // 重绘
    [self setNeedsDisplay:YES];
}

#pragma mark Private
+ (NSShadow*) defaultImageShadow {
    NSShadow* newShadow         = [[NSShadow alloc] init];      
    newShadow.shadowBlurRadius  = 8.0;
    newShadow.shadowOffset      = NSMakeSize(0,-6);
    newShadow.shadowColor       = [NSColor blackColor];
    return [newShadow autorelease];
}

- (void) dealloc {
    self.mainImage       = nil;
    self.backgroundColor = nil;
    self.borderColor     = nil;
    self.imageShadow     = nil;
    [super dealloc];
}

- (void)drawRect:(NSRect)rect {
    // ...
    // 保存图形上下文,应用阴影,绘制图像
    [NSGraphicsContext saveGraphicsState];
    [self.imageShadow set];
    [image drawInRect: imageRect
             fromRect: NSZeroRect
            operation: NSCompositeSourceOver
             fraction: 1.0];
    // 恢复上下文,避免边框有阴影
    [NSGraphicsContext restoreGraphicsState];
    // ...
}
@end

BasicCocoaDrawingAppDelegate.m 中设置 shouldAddShadow 属性为 YES

// 设置视图属性
imageView.mainImage       = [NSImage imageNamed:@"SpaceShuttle"];
imageView.backgroundColor = [NSColor darkGrayColor];
imageView.borderColor     = [NSColor whiteColor];
imageView.shouldAddShadow = YES;

保存所有文件并重新运行应用,图像将带有阴影效果。

5. 绘制渐变背景

在 Cocoa 图形编程中,渐变是一种强大的工具。使用 NSGradient 类创建渐变,它与颜色或阴影的使用方式不同,需要直接绘制到矩形或贝塞尔路径中。 NSGradient 支持多段渐变,可以线性或径向绘制。

添加渐变属性

StyledImageView.h 中添加一个新属性用于渐变背景:

@interface StyledImageView : NSView
@property (retain) NSImage* mainImage;
@property (retain) NSColor* backgroundColor;
@property (retain) NSColor* borderColor;
@property (assign) BOOL shouldAddShadow;
@property (retain) NSGradient* backgroundGradient;
@end

StyledImageView.m 中合成该属性,并在 dealloc 方法中释放:

@implementation StyledImageView
@synthesize mainImage;
@synthesize backgroundColor;
@synthesize borderColor;
@synthesize shouldAddShadow;
@synthesize imageShadow;
@synthesize backgroundGradient;

- (void) dealloc {
    self.mainImage          = nil;
    self.backgroundColor    = nil;
    self.borderColor        = nil;
    self.imageShadow        = nil;
    self.backgroundGradient = nil;
    [super dealloc];
}

将背景绘制代码提取到一个单独的方法中:

@interface StyledImageView ()
// 私有属性
@property (retain) NSShadow* imageShadow;
// 私有方法
+ (NSShadow*) defaultImageShadow;
- (void) drawBackgroundInRect:(NSRect)rect;
@end

@implementation StyledImageView
// ...

- (void) drawBackgroundInRect:(NSRect)rect {
    // 绘制背景颜色
    [self.backgroundColor set]; 
    NSRectFill ( rect );

    // 绘制背景渐变
    [self.backgroundGradient drawInRect:rect angle:90.0];
}

- (void)drawRect:(NSRect)rect {
    NSRect bounds = self.bounds;    
    [self drawBackgroundInRect:bounds];
    CGFloat insetX      = NSWidth     ( bounds ) * 0.10;
    CGFloat insetY      = NSHeight    ( bounds ) * 0.10;
    // ...
}
@end

BasicCocoaDrawingAppDelegate.m 中创建渐变并设置到视图:

StyledImageView* imageView;
imageView = [[StyledImageView alloc] initWithFrame:viewFrame];
// 创建背景渐变
NSColor* gradientBottom = [NSColor colorWithCalibratedWhite:0.18 alpha:1.0];
NSColor* gradientTop    = [NSColor colorWithCalibratedWhite:0.35 alpha:1.0];
NSGradient* gradient = [[NSGradient alloc] initWithStartingColor:gradientBottom
                                                     endingColor:gradientTop];
// 设置视图属性
imageView.backgroundGradient = gradient;

保存所有文件并重新运行应用,视图将显示渐变背景。

总结

通过以上步骤,我们学习了如何在 Cocoa 开发中加载图像、绘制图像、保持图像宽高比、添加阴影效果和绘制渐变背景。这些技巧可以帮助我们创建更加美观和专业的用户界面。

流程图

graph TD;
    A[应用启动] --> B[加载图像];
    B --> C[创建视图];
    C --> D[设置视图属性];
    D --> E[绘制背景颜色];
    E --> F[绘制图像];
    F --> G[绘制图像边框];
    G --> H{是否添加阴影};
    H -- 是 --> I[添加阴影];
    H -- 否 --> J[不添加阴影];
    I --> K[绘制渐变背景];
    J --> K;
    K --> L[显示视图];

表格

功能 相关类 关键方法
图像加载 NSImage +imageNamed:
图像绘制 NSImage -drawAtPoint: -drawInRect:
阴影添加 NSShadow -set
渐变创建 NSGradient -initWithStartingColor:endingColor:

6. 综合应用示例

为了更好地理解上述图像、阴影和渐变的使用,下面给出一个综合应用的示例。假设我们要创建一个带有图像、阴影和渐变背景的窗口界面,以下是详细的实现步骤:

6.1 创建项目和视图类

首先,在 Xcode 中创建一个新的 Cocoa 项目 AdvancedCocoaUI 。然后,添加一个新的 NSView EnhancedImageView.m 及其头文件 EnhancedImageView.h 。在 EnhancedImageView.h 中定义所需的属性:

#import <Cocoa/Cocoa.h>
@interface EnhancedImageView : NSView
@property (retain) NSImage* mainImage;
@property (retain) NSColor* backgroundColor;
@property (retain) NSColor* borderColor;
@property (assign) BOOL shouldAddShadow;
@property (retain) NSShadow* imageShadow;
@property (retain) NSGradient* backgroundGradient;
@end

EnhancedImageView.m 中实现相应的方法:

#import "EnhancedImageView.h"
#import "NSImage-Utilities.h"

@interface EnhancedImageView ()
+ (NSShadow*) defaultImageShadow;
- (void) drawBackgroundInRect:(NSRect)rect;
@end

@implementation EnhancedImageView
@synthesize mainImage;
@synthesize backgroundColor;
@synthesize borderColor;
@synthesize shouldAddShadow;
@synthesize imageShadow;
@synthesize backgroundGradient;

- (id)initWithFrame:(NSRect)frame {
    if ( self = [super initWithFrame:frame] ) {
    }
    return self;
}

- (void)setShouldAddShadow:(BOOL)shouldAdd {
    if ( shouldAddShadow == shouldAdd ) return;
    shouldAddShadow = shouldAdd;
    if ( shouldAddShadow )
        self.imageShadow = [[self class] defaultImageShadow];   
    else
        self.imageShadow = nil;
    [self setNeedsDisplay:YES];
}

+ (NSShadow*) defaultImageShadow {
    NSShadow* newShadow = [[NSShadow alloc] init];      
    newShadow.shadowBlurRadius = 8.0;
    newShadow.shadowOffset = NSMakeSize(0,-6);
    newShadow.shadowColor = [NSColor blackColor];
    return [newShadow autorelease];
}

- (void) dealloc {
    self.mainImage = nil;
    self.backgroundColor = nil;
    self.borderColor = nil;
    self.imageShadow = nil;
    self.backgroundGradient = nil;
    [super dealloc];
}

- (void) drawBackgroundInRect:(NSRect)rect {
    [self.backgroundColor set]; 
    NSRectFill ( rect );
    [self.backgroundGradient drawInRect:rect angle:90.0];
}

- (void)drawRect:(NSRect)rect {
    NSRect bounds = self.bounds;    
    [self drawBackgroundInRect:bounds];
    CGFloat insetX = NSWidth ( bounds ) * 0.10;
    CGFloat insetY = NSHeight ( bounds ) * 0.10;    
    NSRect imageRect = NSInsetRect ( bounds, insetX, insetY );
    NSImage* image = self.mainImage;
    imageRect = [image proportionalRectForTargetRect:imageRect];
    [NSGraphicsContext saveGraphicsState];
    if (shouldAddShadow) {
        [self.imageShadow set];
    }
    [image drawInRect: imageRect
             fromRect: NSZeroRect
            operation: NSCompositeSourceOver
             fraction: 1.0];
    [NSGraphicsContext restoreGraphicsState];
    [self.borderColor set];
    NSBezierPath* imageFrame = [NSBezierPath bezierPathWithRect:imageRect];
    imageFrame.lineWidth = 4;
    [imageFrame stroke];
}
@end
6.2 配置应用委托

AdvancedCocoaUIAppDelegate.m 中配置应用启动时的操作:

#import "AdvancedCocoaUIAppDelegate.h"
#import "EnhancedImageView.h"

@implementation AdvancedCocoaUIAppDelegate
@synthesize window;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    NSRect viewFrame = [self.window.contentView bounds];
    EnhancedImageView* imageView;
    imageView = [[EnhancedImageView alloc] initWithFrame:viewFrame];
    imageView.mainImage = [NSImage imageNamed:@"SpaceShuttle"];
    imageView.backgroundColor = [NSColor darkGrayColor];
    imageView.borderColor = [NSColor whiteColor];
    imageView.shouldAddShadow = YES;
    NSColor* gradientBottom = [NSColor colorWithCalibratedWhite:0.18 alpha:1.0];
    NSColor* gradientTop = [NSColor colorWithCalibratedWhite:0.35 alpha:1.0];
    NSGradient* gradient = [[NSGradient alloc] initWithStartingColor:gradientBottom
                                                         endingColor:gradientTop];
    imageView.backgroundGradient = gradient;
    NSInteger resizingMask = ( NSViewWidthSizable | NSViewHeightSizable );
    [imageView setAutoresizingMask:resizingMask];
    [self.window.contentView addSubview:imageView];
    [imageView release];
}
@end
6.3 运行项目

保存所有文件并运行项目,你将看到一个带有渐变背景、图像、边框和阴影的窗口界面。

7. 注意事项和优化建议

7.1 资源管理

在使用图像、阴影和渐变时,要注意资源的管理。例如,在 dealloc 方法中释放所有的对象引用,避免内存泄漏。同时,对于频繁使用的图像,可以考虑进行缓存,以提高性能。

7.2 性能优化
  • 图像加载 :尽量在应用启动时一次性加载所有需要的图像,避免在绘制过程中重复加载。
  • 阴影处理 :由于保存和恢复图形上下文有一定的性能开销,在不需要阴影时,尽量避免调用 saveGraphicsState restoreGraphicsState
  • 渐变绘制 :对于复杂的渐变效果,要注意控制渐变的复杂度,避免过度绘制。

8. 扩展功能

8.1 动画效果

可以为图像、阴影或渐变添加动画效果,例如图像的淡入淡出、阴影的动态变化等。可以使用 NSAnimation 或 Core Animation 来实现这些效果。

8.2 用户交互

添加用户交互功能,例如点击图像、拖动图像等。可以通过重写 mouseDown mouseDragged 等方法来实现。

流程图

graph TD;
    A[创建项目] --> B[添加视图类];
    B --> C[定义视图属性];
    C --> D[实现视图方法];
    D --> E[配置应用委托];
    E --> F[设置视图属性];
    F --> G[绘制背景渐变];
    G --> H[绘制图像];
    H --> I{是否添加阴影};
    I -- 是 --> J[添加阴影];
    I -- 否 --> K[不添加阴影];
    J --> L[绘制图像边框];
    K --> L;
    L --> M[运行项目];

表格

注意事项 优化建议
资源管理 dealloc 方法中释放对象引用,缓存频繁使用的图像
图像加载 应用启动时一次性加载图像,避免重复加载
阴影处理 不需要阴影时避免保存和恢复上下文
渐变绘制 控制渐变复杂度,避免过度绘制

通过以上内容,我们全面学习了在 Cocoa 开发中图像、阴影和渐变的使用方法,以及如何综合应用这些技术创建美观的用户界面。同时,我们也了解了一些注意事项和扩展功能,希望这些内容能帮助你在实际开发中更好地运用这些技术。

内容概要:本文围绕“基于数据驱动的 Koopman 算子的递归神经网络模型线性化,用于纳米定位系统的预测控制研究”展开,提出了一种结合Koopman算子理论递归神经网络(RNN)的数据驱动建模方法,旨在对非线性纳米定位系统进行有效线性化建模,并实现高精度的模型预测控制(MPC)。该方法利用Koopman算子将非线性系统映射到高维线性空间,通过递归神经网络学习系统的动态演化规律,构建可解释性强、计算效率高的线性化模型,进而提升预测控制在复杂不确定性环境下的鲁棒性跟踪精度。文中给出了完整的Matlab代码实现,涵盖数据预处理、网络训练、模型验证MPC控制器设计等环节,具有较强的基于数据驱动的 Koopman 算子的递归神经网络模型线性化,用于纳米定位系统的预测控制研究(Matlab代码实现)可复现性和工程应用价值。; 适合人群:具备一定控制理论基础和Matlab编程能力的研究生、科研人员及自动化、精密仪器、机器人等方向的工程技术人员。; 使用场景及目标:①解决高精度纳米定位系统中非线性动态响应带来的控制难题;②实现复杂机电系统的数据驱动建模预测控制一体化设计;③为非线性系统控制提供一种可替代传统机理建模的有效工具。; 阅读建议:建议结合提供的Matlab代码逐模块分析实现流程,重点关注Koopman观测矩阵构造、RNN网络结构设计MPC控制器耦合机制,同时可通过替换实际系统数据进行迁移验证,深化对数据驱动控制方法的理解应用能力。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值