Vala プログラミング

WebGPU プログラミング

おなが@京都先端科学大

GNUstep CoreGraphics (2)

前回報告した ProjectCenter を使って作成した CoreGraphics のプログラムです。

実行結果
起動時の画面 (Draw1)

Draw2(メニューから Draw → Draw2 を選択)

Draw3

Draw4

プログラム
(プロジェクト生成時以降、編集したプログラム)
Headers
AppController.h

#ifndef _PCAPPPROJ_APPCONTROLLER_H
#define _PCAPPPROJ_APPCONTROLLER_H

#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import "QEB2OpenGLView.h"

@interface AppController : NSObject
{
    NSWindow *window;
    QEB2OpenGLView * openGLView;
}

+ (void)  initialize;

- (id) init;
- (void) dealloc;

- (void) awakeFromNib;

- (void) applicationDidFinishLaunching: (NSNotification *)aNotif;
- (BOOL) applicationShouldTerminate: (id)sender;
- (void) applicationWillTerminate: (NSNotification *)aNotif;
- (BOOL) application: (NSApplication *)application
              openFile: (NSString *)fileName;

- (void) showPrefPanel: (id)sender;
- (void) drawAction1: (id)sender;
- (void) drawAction2: (id)sender;
- (void) drawAction3: (id)sender;
- (void) drawAction4: (id)sender;

@end

#endif

QEB2OpenGLView.h (新しく生成)

#import <GL/gl.h>
#import <GL/glu.h>
#import <cairo/cairo.h>

#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>

@interface QEB2OpenGLView : NSOpenGLView
{
   cairo_surface_t * _cairoSurface;
   CGContextRef _opalContext;
   GLuint _texture;
   CGRect rootRect;
}

- (void) draw1;
- (void) draw2;
- (void) draw3;
- (void) draw4;
- (void) reDraw;
- (void) clearContextWithRect:(CGRect)rect;
@end

Classes
AppController.m

#import "AppController.h"

#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>

@implementation AppController

+ (void) initialize
{
   NSMutableDictionary *defaults = [NSMutableDictionary dictionary];

   /*
    * Register your app's defaults here by adding objects to the
    * dictionary, eg
    *
    * [defaults setObject:anObject forKey:keyForThatObject];
    *
    */
  
   [[NSUserDefaults standardUserDefaults] registerDefaults: defaults];
   [[NSUserDefaults standardUserDefaults] synchronize];
}

- (id) init
{
   if ((self = [super init]))
   {
   }
      return self;
}

- (void) dealloc
{
   RELEASE (openGLView);
   RELEASE (window);
   [super dealloc];
}

- (void) awakeFromNib
{
}

- (void) applicationDidFinishLaunching: (NSNotification *)aNotif
{ 
   openGLView = [[QEB2OpenGLView alloc]
               initWithFrame: [[window contentView] frame]
                   pixelFormat: [QEB2OpenGLView defaultPixelFormat]];
  
   [window setContentView: openGLView];
  
   [openGLView reDraw];
}

- (BOOL) applicationShouldTerminate: (id)sender
{
   return YES;
}

- (void) applicationWillTerminate: (NSNotification *)aNotif
{
}

- (BOOL) application: (NSApplication *)application
       	      openFile: (NSString *)fileName
{
   return NO;
}

- (void) showPrefPanel: (id)sender
{
}

- (void) drawAction1: (id)sender
{
   NSLog(@"drawAction1");
   [openGLView draw1];
}

- (void) drawAction2: (id)sender
{
   NSLog(@"drawAction2");
   [openGLView draw2];
}

- (void) drawAction3: (id)sender
{
   NSLog(@"drawAction3");
   [openGLView draw3];
}

- (void) drawAction4: (id)sender
{
   NSLog(@"drawAction4");
   [openGLView draw4];
}

@end

QEB2OpenGLView.m (新しく生成)

#import "QEB2OpenGLView.h"

#define TEXTURE_TARGET GL_TEXTURE_2D

#define PI 3.14159265358979323846

CGContextRef opal_new_CGContext(cairo_surface_t *target, CGSize device_size);

void drawRandomPaths(CGContextRef context, int w, int h)
{
   for (int i = 0; i < 20; i++) {
      int numberOfSegments = rand() % 8;
      int j;
      CGFloat sx, sy;
        
      CGContextBeginPath(context);
      sx = rand()%w; sy = rand()%h;
      CGContextMoveToPoint(context, rand()%w, rand()%h);
      for (j = 0; j < numberOfSegments; j++) {
         if (j % 2) {
            CGContextAddLineToPoint(context, rand()%w, rand()%h);
         }
         else {
            CGContextAddCurveToPoint(context, rand()%w, rand()%h,  
               rand()%w, rand()%h,  rand()%h, rand()%h);
         }
      }
      if(i % 2) {
         CGContextAddCurveToPoint(context, rand()%w, rand()%h,
            rand()%w, rand()%h,  sx, sy);
         CGContextClosePath(context);
         CGContextSetRGBFillColor(context, (CGFloat)(rand()%256)/255, 
            (CGFloat)(rand()%256)/255, (CGFloat)(rand()%256)/255, 
            (CGFloat)(rand()%256)/255);
         CGContextFillPath(context);
      }
      else {
         CGContextSetLineWidth(context, (rand()%10)+2);
         CGContextSetRGBStrokeColor(context, (CGFloat)(rand()%256)/255, 
            (CGFloat)(rand()%256)/255, (CGFloat)(rand()%256)/255, 
            (CGFloat)(rand()%256)/255);
         CGContextStrokePath(context);
      }
   }
}

@implementation QEB2OpenGLView

- (void) draw1
{
   NSLog(@"GLView draw1");
   [self clearContextWithRect:rootRect];
   
   int width  = rootRect.size.width;
   int height = rootRect.size.height;
  
   for (int i = 0; i < 20; i++) {
      if(i % 2) {
         CGContextSetRGBFillColor(_opalContext, (CGFloat)(rand()%256)/255, 
            (CGFloat)(rand()%256)/255, (CGFloat)(rand()%256)/255, 
            (CGFloat)(rand()%256)/255);
         CGContextFillRect(_opalContext, CGRectMake(rand()%width,
            rand()%height, rand()%width, rand()%height));
      } 
      else {
         CGContextSetLineWidth(_opalContext, (rand()%10)+2);
            CGContextSetRGBStrokeColor(_opalContext, (CGFloat)(rand()%256)/255, 
            (CGFloat)(rand()%256)/255, (CGFloat)(rand()%256)/255, 
            (CGFloat)(rand()%256)/255);
         CGContextStrokeRect(_opalContext, CGRectMake(rand()%width,
            rand()%height, rand()%width, rand()%height));
      }
   }

   [self reDraw];
}

- (void) draw2
{
   NSLog(@"GLView draw2");
   [self clearContextWithRect:rootRect];
   
   int width  = rootRect.size.width;
   int height = rootRect.size.height;
  
   // Draw random circles (some stroked, some filled)
   for (int i = 0; i < 20; i++) {
      CGContextBeginPath(_opalContext);
      CGContextAddArc(_opalContext, rand()%width, rand()%height,
         rand()%((width>height) ? height : width), 0, 2*PI, 0);
      CGContextClosePath(_opalContext);

      if(i % 2) {
         CGContextSetRGBFillColor(_opalContext,
            (CGFloat)(rand()%256)/255, (CGFloat)(rand()%256)/255,
            (CGFloat)(rand()%256)/255, (CGFloat)(rand()%256)/255);
         CGContextFillPath(_opalContext);
      }
      else {
         CGContextSetLineWidth(_opalContext, (rand()%10)+2);
         CGContextSetRGBStrokeColor(_opalContext,
            (CGFloat)(rand()%256)/255, (CGFloat)(rand()%256)/255,
            (CGFloat)(rand()%256)/255, (CGFloat)(rand()%256)/255);
         CGContextStrokePath(_opalContext);
      }
   }
   
   [self reDraw];
}

- (void) draw3
{
   NSLog(@"GLView draw3");
  [self clearContextWithRect:rootRect];
   
   drawRandomPaths(_opalContext, rootRect.size.width,
      rootRect.size.height);
      
   [self reDraw];
}

- (void) draw4
{
   NSLog(@"GLView draw4");
   [self clearContextWithRect:rootRect];
   
   int width  = rootRect.size.width;
   int height = rootRect.size.height;
  
   /* Clipping example - draw random path through a circular clip */
   CGContextBeginPath(_opalContext);
   CGContextAddArc(_opalContext, width/2, height/2,
      ((width>height) ? height : width)/2, 0, 2*PI, 0);
   CGContextClosePath(_opalContext);
   CGContextClip(_opalContext);
        
   // Draw something into the clip
   drawRandomPaths(_opalContext, width, height);
   
   // Draw an clip path on top as a black stroked circle.
   CGContextBeginPath(_opalContext);
   CGContextAddArc(_opalContext, width/2, height/2,
      ((width>height) ? height : width)/2, 0, 2*PI, 0);
   CGContextClosePath(_opalContext);
   CGContextSetLineWidth(_opalContext, 1);
   CGContextSetRGBStrokeColor(_opalContext, 0, 0, 0, 1);
   CGContextStrokePath(_opalContext);
   
   [self reDraw];
   
   OPContextResetClip(_opalContext);
}

-(void) reDraw
{  
   [self reshape];
}

- (void) clearContextWithRect:(CGRect)rect
{
   CGContextSetRGBFillColor(_opalContext, 1, 1, 1, 1);
   CGContextFillRect(_opalContext, rect);
}

- (void) prepareOpenGL
{
   [super prepareOpenGL];
   int width  = [self frame].size.width;
   int height = [self frame].size.height;

   _cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
   _opalContext = opal_new_CGContext(_cairoSurface, CGSizeMake(width, height));
  
   /* Draw some content into the context */
   rootRect = CGRectMake(0, 0, width, height);
   [self clearContextWithRect:rootRect];
   
   // Draw random rectangles (some stroked some filled)
   for (int i = 0; i < 20; i++) {
      if(i % 2) {
         CGContextSetRGBFillColor(_opalContext, (CGFloat)(rand()%256)/255, 
            (CGFloat)(rand()%256)/255, (CGFloat)(rand()%256)/255, 
            (CGFloat)(rand()%256)/255);
         CGContextFillRect(_opalContext, CGRectMake(rand()%width,
            rand()%height, rand()%width, rand()%height));
      } 
      else {
         CGContextSetLineWidth(_opalContext, (rand()%10)+2);
            CGContextSetRGBStrokeColor(_opalContext, (CGFloat)(rand()%256)/255, 
            (CGFloat)(rand()%256)/255, (CGFloat)(rand()%256)/255, 
            (CGFloat)(rand()%256)/255);
         CGContextStrokeRect(_opalContext, CGRectMake(rand()%width,
            rand()%height, rand()%width, rand()%height));
      }
  }
}

- (void) dealloc
{
   cairo_surface_finish(_cairoSurface);
   CGContextRelease(_opalContext);

   [super dealloc];
}

-(void) reshape
{
   NSLog(@"reshaping");
   NSRect rect = [self bounds];
   glViewport(0, 0, rect.size.width, rect.size.height);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluPerspective(60.0, rect.size.width/rect.size.height, 0.2, 7);

   [self setNeedsDisplay:YES];
}

- (void) drawRect: (NSRect)r
{
   [[self openGLContext] makeCurrentContext];
   
   int width = [self frame].size.width;
   int height = [self frame].size.height;
   
   glGenTextures(1, &_texture);
   glBindTexture(TEXTURE_TARGET, _texture);
   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
   unsigned char * data = cairo_image_surface_get_data(_cairoSurface);
   gluBuild2DMipmaps(TEXTURE_TARGET, GL_RGBA, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data);
   
   glViewport(0, 0, [self frame].size.width, [self frame].size.height);
 
   glClear(GL_COLOR_BUFFER_BIT);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();

   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();

   glEnable(GL_TEXTURE_2D);
   glEnable(TEXTURE_TARGET);
   glBindTexture(TEXTURE_TARGET, _texture);
   glColor3f(1,1,1);

   glBegin(GL_QUADS);
   glTexCoord2f(0.0, 1.0);
   glVertex2f(-1.0, -1.0);

   glTexCoord2f(0.0, 0.0);
   glVertex2f(-1.0, 1.0);

   glTexCoord2f(1.0, 0.0);
   glVertex2f(1.0, 1.0);

   glTexCoord2f(1.0, 1.0);
   glVertex2f(1.0, -1.0);
   glEnd();

   [[self openGLContext] flushBuffer];
}

@end

Supporting Files
GNUmakefile.preamble(変更箇所のみ)

# Additional flags to pass to Objective C compiler
ADDITIONAL_OBJCFLAGS += -std=gnu99

# Additional flags to pass to C compiler
ADDITIONAL_CFLAGS += -std=gnu99

# Additional GUI libraries to link
ADDITIONAL_GUI_LIBS += -lm -lGL -lGLU -lopal -lcairo

Interfaces
(プロジェクト名).gorm
変更は下参照


ProjectCenter と Gorm によるプログラム作成
1 ProjectCenter でプロジェクトを作成
 Project Types は Application を選択
 ビルドとランを実行
 [Main Menu] が表示される。
2 window を追加
 Interfaces の (プロジェクト名).gorm をダブルクリック
 Gorm が起動する。
 Gorm のControls window -> Windows から [Window] を
 Resources window の Objects にドロップ
 Window(0) インスタンスが生成される。
 size, attributes (Visual at launch をチェック)を設定する。

 設定を保存する場合
 Gorm メインメニュー -> Document -> Save

 AppController と Window の接続
 Window の View の設定は AppController で行うので、AppController と
 Window を接続する。
(1)AppController Outlet の作成
  (作成した Outlet 名が winsow instance の名前になる、Action は要らない)
  Resources window の Classes で、NSObject -> AppController を選択
  Inspector window の Outlet add ボタンで作成(Outlet名: window)
 (AppController が表示されていない場合は、下のスクロールバーを
  左にする)
(2)接続
  Resources window の Objects で、 AppController(S) から Window(0)(T) に接続
  接続は、[Ctrl]キーを押しながらドラッグする。
  Inspector window で、Outlet window を選択し接続[Connect] する。

3 Draw menu の追加
 interfaces の gorm file をダブルクリックし、 Gorm を起動
 ・[Main Menu](作成プログラムのメニュー)に [Submenu] を追加
  Controls window -> Menus から、 [Submenu>] を [Main Menu] の目的の
  場所にドラッグする。
  [Submanu] が追加され、メニューに [item] が1個できる。
  Item 名を変更する。(Draw1 など)
  ( 2つ目い以降は、 Menus の [item] をドッラグして追加する)
 ・Outlet/Action の追加と接続
  [Submenu] のコントロールは AppController で行うようにするので、
  [Submenu] を AppController に接続する。
  *Outlet/Action の追加
  AppController に
   Outlet: draw1, draw2, draw3, draw4
   Action: drawAction1:, drawAction2:, drawAction3:, drawAction4
  を追加する。
  *接続
  [Main Menu] の Draw1(draw1) を AppController(drawAction1:) に接続
  接続方法は window の場合と同様。(残りのメニューも同様に接続する)

4 プログラムの生成と編集
 QEB2OpenGLView.h, QEB2OpenGLView.m の生成と編集
 (新しく作成したクラスファイルのプロジェクトへの追加は、Classes を
  選択しメニューから Project -> Add Files を実行する。)
 AppController.h, AppController.m の編集
 GNUmakefile.preamble の編集