2012/11/27

GDI+プログラミング

この記事はC/C++の言語本を一通り読み、Windowsプログラミングに多少慣れてきた初学者向け兼備忘録として記載しています。
また、この記事は2012/11/26時点の情報によって記述されています。
C++ビルド環境はVS2005を使用しています。

GDI+プログラミングと銘打っていますが、別段GDI+の全般を使うわけでもなく、単純にGDI+を使用して一般的な画像フォーマットの読み込み実装を外部ライブラリに任せてしまおうというのが主旨です。
gdiplus.dllはXP以降であればOSに標準で付属するとのことなので、XP以降対応であれば、十分選択肢に入る外部ライブラリだと思います。


2012/08/18

SDLの使い方(画像の読込と画像データへの参照)

SDL単体ではBMP形式のみ対応しており、png,jpgやtiff等の別形式の画像の読み込みを行う場合SDL_imageを導入します。

SDL_imageで画像を読み込む場合は以下のようにIMG_Load関数を呼び出します。

[cpp]
if( GetFileAttributes( "test.png" ) != -1 )
{
SDL_Surface* image;
image = IMG_Load( "test.png" );

if( image == NULL )
{
sprintf( aszMessage, TEXT("画像の読み込みに失敗しました:%s"), SDL_GetError() );
OutputDebugString( aszMessage );
exit(1);
}
}
[/cpp]

IMG_Load関数が成功するとSDL_Surfaceオブジェクトへのポインタが返ってきます。このオブジェクト内に読み込んだファイルの画像データが保存されています。
このSDL_SurfaceオブジェクトをSDLのBlitSurface等に指定することで、画面に描画することができます。

少し話が変わりますが、各画像形式の読み込み処理を全て実装するのは手間なので、ライブラリ側に任せたいと考えてSDLに手を出した方もいらっしゃるかもしれません。

これは私がそうなのですが。GDI+を使用するという選択しもあったんですが、音関連の処理もライブラリに任せて本体のロジックのみ集中したい、そしていくつもライブラリをかき集めて管理するのが面倒ということでSDLを選択したわけなんですが、SDLには画像の読み込みだけしてもらってデータだけ欲しいと期待していました。

しかし、IMG_Loadを実行して帰ってくるのは隠蔽されたオブジェクトであるSDL_Surface。まぁ当然ですよね。 これを何とかしてデータだけ抽出したいと思い、SDL_Surfaceの構造を調査したところ割と簡単に見つけました。

結論からいうとSDL_Surface:Pixelsに画像データの先頭アドレスが格納されています。
これにwidth,heightの画像情報を合わせてビットマップを作成してやれば、異なる画像形式のファイルでも必要な画像データと画像情報が手に入ります。

そんなわけで、SDL_Surface::Pixelsの内容をSetPixel()関数を使用して画像に直接描画した画像が下図になります。



[cpp]
if( m_image != NULL )
{
int r, g, b;
for ( int i = 0; i < m_image->h; i++ ) {
for ( int j = 0; j < m_image->w; j++ ) {
// 1画素の R, G, B 成分は1バイトであること.画像データは隙間無くならんでいること.R, G, B の順に並んでいることを仮定している.
unsigned char* p = (unsigned char*)m_image->pixels;
int pixel_at = (i * (m_image->w) + j ) * m_image->format->BytesPerPixel;
r = p[pixel_at];
g = p[pixel_at + 1];
b = p[pixel_at + 2];

SetPixel( hdc, j, i, RGB( r, g, b ) );
}
}
}
[/cpp]

どこかでみた構図と画像ですが、これはpng画像を読み込んで画像データに直接操作できたことを証明できたことになるため、検証としては大きな成果です。

ただ、pngでは画像データの並びが"RGB,RGB,RGB,RGB..."と続くので何も問題はないのですが、bmpを読み込むと画像データの並びが"BGR,BGR,BGR,BGR..."と逆転しているので、読み込み後は拡張子に基づいて自前で調整処理を実装しないといけなさそうです。

描画処理についてはこちらのサイトのソースコードを参考に改変いたしました。
Linux で動く,SDL_image を用いて画像ファイルを読み込むプログラム例



12/09/13追記

SDLからRawデータを取得して画像処理を行い、再びSDL_Surfaceに押し込むことで画像処理のみ記述するという使い方も用途としてはお手軽にできるかもしれない。

SDL_imageの導入方法(libpng/zlibの静的リンクビルド)

SDL_imageを導入する時、気を付けなければいけないことについて備忘録として残す。

導入方法と言いつつ、ダウンロードしてSDLと関連付けてビルドするところまで端折る。なぜなら、他のサイトで画像つきで解説しているので冗長だから。

当記事では以下の環境を使用しています。
SDL_image1.2.12
SDLのバージョン1.2

まず、SDL_imageをHGからCloneしてSDLとリンクさせてビルドさせる場合、pngやjpgを読み込むときにexeと同じ位置に、SDL_image\VisualC\external\lib\x86にあるlibpng15-15.dllやlinjpeg-8.dllを配置しないといけない。

基本的にdllを配置して実行環境を構築したくないので、staticlibでビルドする方法を考える。

まず、どこでDLLの読み込み指定を行っているのか?
これはSDL_imageのプロジェクトプロパティのプリプロセッサ項目に以下のような項目がある。

(一部抜粋)

[html]
LOAD_JPG_DYNAMIC=\"libjpeg-8.dll\"
LOAD_PNG_DYNAMIC=\"libpng15-15.dll\"
LOAD_TIF_DYNAMIC=\"libtiff-5.dll\"
LOAD_WEBP_DYNAMIC=\"libwebp-2.dll\"
PNG_USE_DLL
ZLIB_DLL
[/html]

これらの項目が定義されているとSDL_imageは実行時にDLLを読み込もうとするので、これらの定義を削除してやれば外部参照が未解決状態になり、各ライブラリへのリンクを求められる状態になります。

ここでは、ライセンス的に安全とわかっているlibpngのみ削除してstaticlibでリンクします。
削除する項目は「LOAD_PNG_DYNAMIC~」と「PNG_USE_DLL」「ZLIB_DLL」です。後者二つのプリプロセッサはプロジェクトで使用していないようですが、 DLLの使用を示唆する記述は削除しておきました。

この状態までくれば後は必要なライブラリを関連付けしてビルドしてやれば静的リンクを行ったバージョンのSDL_image.libが出来上がります。SDL_image.libをリンクしたプロジェクト内でlibpng.libとzlib.libをリンクすることでも同様に実現可能でしょう。この辺りはケースバイケースなので、都合がいい方を選択するといいと思います。

最後になりますが、静的リンクを行う際にはライセンスを必ず確認してください。

今回はzlib/libpngライセンスを使用しているlipngとzlibのみ使用したので問題になりませんが、もしGPL/LGPLだった場合、ソースを静的にリンクするとライセンスに感染してしまうのでフリーウェアとして開発していない場合は注意してください。

2012/08/17

SDLイベントキューの実装を解析

SDLではOSが送ってくるウィンドウメッセージをいったんSDL側で全て吸収してSDL独自形式に変換した上で、SDL_PollEventを使用してユーザー側に渡して処理をさせます。
ここではそのウィンドウメッセージをどう処理しているのかコードを辿っていきたいと思う。

まず、SDLにおけるウィンドウプロシージャの実装はSDL_sysevents.cに定義されているWinMessage関数内で行われている。
ここから様々なサブシステムにメッセージを投げて処理をしていると思われるがウィンドウプロシージャ側から辿るのが難しそうなので逆方向からアプローチを行う。

ユーザーがイベントを取得するSDL_PollEvent側から辿ってみる。
SDL_PollEvent(正確にはSDL_PeepEvent)で使用するSDL_EventQ(イベントキュー)はグローバルメンバ変数で、SDL_events.cに次のように定義されている

[cpp]
/* SDL_events.c */
/* Private data -- event queue */
#define MAXEVENTS 128
static struct {
SDL_mutex *lock;
int active;
int head;
int tail;
SDL_Event event[MAXEVENTS];
int wmmsg_next;
struct SDL_SysWMmsg wmmsg[MAXEVENTS];
} SDL_EventQ;
[/cpp]

構造体の仕様から、SDLの内部処理でeventメンバに対して随時ウィンドウメッセージを処理したイベントを追加されることが予想される。では、実際にどこでイベントが追加されるかというとSDL_AddEvent内で処理される。

簡単に流れを整理するとWinMessage>(SDL入力処理?)>SDL_AddEvent>SDL_PollEventの順に処理されることでメッセージをやり取りするのではないかと推測できる。

次はSDL_AddEventをだれが呼び出しているかについて調べていきたい。
以降から多少複雑になるが、SDL_AddEventを呼び出している処理はSDL_PollEvent内でも呼び出しているSDL_PeepEventsによって追加処理も行われる。
これはSDL_PeepEventsと引数であるSDL_eventactionが次のような定義になっているためである。
定義を見るとSDL_ADDEVNETを渡せばイベントを登録するし、それ以外であればイベントを取得する処理になるのがわかる。

[cpp]
int SDL_PeepEvents(SDL_Event *events, int numevents, SDL_eventaction action,
Uint32 mask)

/* SDL_eventactionの定義 * /
typedef enum {
SDL_ADDEVENT,
SDL_PEEKEVENT,
SDL_GETEVENT
} SDL_eventaction;
[/cpp]

ではさらに辿ってSDL_PeepEventに対してSDL_ADDEVENTを渡している処理を追ってみる。
SDL_PeepEventをさらにラップしているSDL_PushEventを経由して多岐にわたるソースコードからイベントが登録されている。
SDL_PushEventを使用してイベントを登録しているサブシステムは以下の通り。

  • sdl_mouse.c

  • sdl_keyboard.c

  • sdl_joystick.c

  • sdl_events.c

  • sdl_resize.c

  • sdl_quit.c

  • sdl_expose.c

  • sdl_active.c

説明は省くがsdl_mouse、sdl_keyboardやsdl_joystick等入力処理からの登録がほとんどである。
これを集約してさらに元をたどるとWinMessageに行き当たる。

以上でSDLがウィンドウプロシージャからのメッセージをどの様にユーザーに提供しているのか大まかに把握できた。

SBL_PollEventの実装を解析

結論から言ってしまうとSBL_PollEventの中身は通常のWin32アプリケーションで実装されるメッセージポンプと同様の処理を行うSDL_PumpEventsとメッセージキューに貯まっているウィンドウズメッセージを横取りするSBL_PeepEventsによって実装されています。

SDL_PumpEventsの実装を辿っていくと最終的に次のコードが見つかります。
これは典型的なメッセージポンプ処理の実装です。

[cpp]
// SDL_dibevents.c
void DIB_PumpEvents(_THIS)
{
MSG msg;

while ( PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE) ) {
if ( GetMessage(&msg, NULL, 0, 0) > 0 ) {
DispatchMessage(&msg);
}
}

if ( SDL_GetAppState() & SDL_APPMOUSEFOCUS ) {
DIB_GenerateMouseMotionEvent( this );
}
}
[/cpp]

SBL_PeepEventの実装を辿っていくと「SBLが管理しているイベントキューの中にある全てのイベント種類の中から最初の1つを取得する」という動作を行います。取得したイベントはSDL_CutEventが呼び出され、メッセージキューから消えます。



余談ですが、spot=(spot+1)%MAXEVENTの式があり、MAXEVENTS=128の指定のため、おそらくSDLのイベントキューのサイズは128だろうと推測する。

SDLの使い方

SuzudoraExの開発に当たり、SDLを導入してIO周りの処理にかかるコストを削減することにしたわけだが、SDLの実装方法について毎回調べるのは不毛だと気付いたので実装メモを残していこうと思う。

まず、WindowsだろうがLinuxだろうがインストール方法は別サイトにいくらでもあるので除外。
ここではライブラリが開発環境に組み込まれたことを前提とする。
また、SDLmain.libライブラリはOSによるスタートアップ(エントリポイント)の依存コードを減らすために組み込むライブラリなので、当サイトでは扱わないものとする。

メモにあるソースコードを見るに当たり、SDLの関数リファレンスを参照してほしい。

SDL最少コード


以下のコードは全く意味のないコードだがSDLを使用するための必要最低限のコードである。

[cpp]
if( SDL_Init( SDL_INIT_VIDEO ) != 0){
exit(1);
}

SDL_Quit();
[/cpp]

解説としてはSDL_InitでSDLサブシステムとして登録されているシンボルを指定して初期化する。
これを行わないと各サブシステムの関数呼び出し時にエラーが発生するらしい。
ここで出てくるSDL_INIT_VIDEO等複数初期化したい場合はSDL.hに下記のように定義してあるので、論理和で結合してSDL_Initに渡してやればいい。もしくはSDL_INIT_EVERYTHINGで初期化する。

SDL_Quit()は後始末処理を行っているのでアプリケーション終了時に必ず呼び出すこと。

[cpp]
// SDL.h
#define SDL_INIT_TIMER 0x00000001
#define SDL_INIT_AUDIO 0x00000010
#define SDL_INIT_VIDEO 0x00000020
#define SDL_INIT_CDROM 0x00000100
#define SDL_INIT_JOYSTICK 0x00000200
#define SDL_INIT_NOPARACHUTE 0x00100000 /**< Don't catch fatal signals */
#define SDL_INIT_EVENTTHREAD 0x01000000 /**< Not supported on all OS's */
#define SDL_INIT_EVERYTHING 0x0000FFFF
[/cpp]

SDL_Initはまとめて論理和で結合して渡してもいいし、SDL_Initを複数回呼び出していい。
重複するシンボルがあったとしても既に登録してあれば無視されるので安全が保たれている。
しかし注意したいのは初期化後、何らかの処理を行った後にさらにSDL_Initを使用する場合だ。

SDL_Initは内部で次の3つの処理を行っている。そのため、エラーメッセージがクリアされてしまう場合があるため、(逆に言えば気にしないでいいならばSDL_Initを使用してもいい)素直にSDL_InitSubSystemを使用して初期化したほうがいいだろう。

  • SDL_Init処理

    • エラーメッセージのクリア

    • SDL_InitSubSystemの呼び出し

    • パラシュートコードの登録

SDL_InitSubSystem の使用方法としては「スタートアップではSDL_INIT_VIDEOだけ初期化したけど、あとでジョイスティックの実装をすることになった。でもSDL_Initに付け足すとコードファイルが別々になって後々修正が大変」というこ時にジョイスティックの実装開始地点もしくは初期化処理にSDL_InitSubSystem にSDL_INIT_JOYSTICKを指定して初期化するもののようだ。

SDLのウィンドウ使用しないでSDLオブジェクトを利用する方法


SDLではSDLが用意した初期化処理を行い、SDLが生成するウィンドウを使用して各メソッド・オブジェクトを弄ることになる。こうすると、ユーザー側ではWindowsやLinux等のOSレベルのネイティブな処理を考えなくてよくなるが、自前で画像を加工しようと思っている筆者からすると有難迷惑にもほどがある。何とかSDLが生成するウィンドウを使用せずに、画像の読み込みを行ったオブジェクトの生データを横取りできないか考える。

調査したところ、割と簡単そうだった。
SDL_imageライブラリを導入した上で、Load_IMG関数を使用することによってSDL_Surface*が返ってくる。このSDL_Surfaceの公開メンバにwidth,height,picth等画像情報が含まれている。

これが一番 重要だが、SDL_Surface::pixelsの中身は画像データ配列の先頭アドレスが入っている。
これらを利用することで、 各画像形式の読み込み処理を意識せずにデータのみ横取りすることができると予想される。

まだ未検証なので、次の更新までには確認してみたい。
[追記] 確認が取れました。こちらで実際に読み込んだデータを直接参照して描画できることを確認しています。

もし可能であるならば、SDLを意識せずにDIBを作成するラッパークラス等を作ってみたいと思う。

SDLウィンドウのウィンドウプロシージャを乗っ取る


SDLが生成するウィンドウからウィンドウプロシージャをフックするのは割と簡単。

SDL内部のグローバルな変数に"SDL_Window"があるので、これをGetWindowLong/SetWindowLongのターゲットに指定するとウィンドウプロシージャの乗っ取りができる。

しかし、乗っ取ったからと言って何か利便性があるのかと考える微妙である。
まぁ、SDLがサポートしていないウィンドウメッセージをどうしても使いたいという場合は乗っ取ったウィンドウプロシージャで処理してそれ以外はSDLのウィンドウプロシージャに丸投げする。という使い方ならできるかもしれない。

GWL_USERDATAは使用できると面白そうだけど、SDL内でオブジェクトのやり取りに使われていないことを確認しないと危なくて使えない。

何か有用性があればぜひ教えてほしい。

2012/02/10

リトルエンディアン・バイトエンディアン バイトオーダー相互変換関数

PSDを読み込むとき、バイトオーダーがビッグエンディアンなので自前でリトルエンディアン・ビッグエンディアンの相互変換マクロを作って読み込み・書き込みをしていたが、どうも標準でCライブラリに搭載されているらしい。

  「リトルエンディアン」 「ビッグエンディアン」 のキーワードで検索するから見つからないらしく、これらの別名として 「ホストバイトオーダー」 「ネットワークバイトオーダー」 というキーワードがあり、こちらで検索すると相互変換関数が見つかる。



 それでも用意されている関数だけだと16bit(2byte)と32bit(4byte)の値しか扱えないので、自前で実装する余地が残っているのが救いか?

ネットワークバイトオーダー<->ホストバイトオーダー変換関数(BYTEORDER関数)




  • #include <arpa/inet.h>

  • u_long htonl (u_long hostlong) : 32 ビットホストバイトオーダーをネットワークバイトオーダーに変換します

  • u_short htons (u_short hostshort) : 16 ビットホストバイトオーダーをネットワークバイトオーダーに変換します

  • u_long ntohl (u_long netlong) : 32 ビットネットワークバイトオーダーをホストバイトオーダーに変換します

  • u_short ntohs (u_short netshort) : 16 ビットネットワークバイトオーダーをホストバイトオーダーに変換します


 また、隣接するバイトを変換するswab(swap byte)関数が用意されている。



  • #include <string.h>

  • void swab(const void *from, void *to, size_t n) : from で指された配列から n バイトを to で指された配列に、隣接した偶数/奇数バイトを交換しながらコピーする。この関数は異なるバイトオーダーを持つマシン間でのデータ交換に使用される

2012/02/04

C++によるlibpngを使用したpngファイルの読込と表示・描画メモ

掲載予定のサンプルソースコードは現在調整中です
環境情報
WindowsXP SP3
VisualStduio2005 SP1

本稿の対象者


 自分でlibpngを利用したpng読込・書込処理を実装したいWindows(VisualStudio)ユーザー。足がかりになるための資料として活用してもらえることを目標に投稿しています。
 必要最低限のライブラリのみ参考にしたいというのであれば、libpngの中の下記ディレクトリにWindows/VisualStudio向けにPngの読み込み・書き込み実装されているlibpngライセンスのソースコードがありますので、そちらを導入して微調整をするといいでしょう。


  • libpng/contrib/visupng/PngFile.c

  • libpng/contrib/visupng/PngFile.h


 単純にC/C++でpngを手軽に読み込みたい場合はSDLやGDI+等の画像ライブラリを使用することをお勧めします。

導入


 sourceforgeから最新版のlibpngとzlibをダウンロードしてください。

http://sourceforge.net/projects/libpng/files/

 解凍後はlibpng-x.x.xとzlib-x.x.xはフォルダのバージョン名等をリネームして「libpng」「zlib」というフォルダ名で同レベルの階層に配置してください。

 次に、libpng/projects/visualc71/libpng.slnを開いてください。VC6.0以前だと開けない場合がありますが、その場合は独自にプロジェクトを作成してコンパイル環境を構築するか、無料版のVSの最新版を利用してlibpng/projects/vstudio/vstudio.slnを利用してください。
 後はビルド構成をDebug/Releaseに切り替えてリビルドすれば、libpngd.libとlibpng.libが生成されます。

 プロジェクトで使用するにはプロジェクトに直接png.h,zlib.h,zconf.h等のヘッダファイルを取り込み、pragmaプリプロセッサかプロジェクトの依存ライブラリにlibpng.libをリンクしてください。詳細な手順は別サイトで画像付で詳しく書かれていると思いますので、そちらに任せます。


自力解決に向けてのアドバイス


 libpng,zlib等の3rdparty製のライブラリの使用方法をネットで探して実装しようとしている人はネットに落ちていない問題が出ると、結構な確率で開発に行き詰ります。その時に状況を打破するためのアドバイスを一つ。
 libpngのフォルダ内にはpng導入用のテストコードやマニュアルが用意されています。それを順に追っていくことで、そもそもlibpngの開発者たちがどのように使うことを想定しているかを知ることができます。
 英語だから読めない?技術文書は文学作品と違い、形容詞の微妙なニュアンスがわからなくても理解することが可能です。
 プログラマーという人種は白黒つけるのが職業病みたいなもので、誤訳が少なくなるようにわかりづらい文法、一般的でない慣用句はほぼ使用されません。そのため、ネット翻訳や辞書を使えばそれなりの精度で訳してもくれます。
 文書を完全に訳せなくとも、開発者たちが何を言いたいのかわかるレベルさえあれば理解可能です。
 我々には言語や文化の差を埋めてくれるプログラム言語という共通言語があります。
 本質的に何が言いたいのかは、ソースコードに書いてあるのでそこから読み取ればいいだけです。

 さて、また脱線しましたがlibpngでは下記ファイルを基点に調査してみてください。


  • contrib/visupng - このフォルダのPngFile.cはWindowsの開発環境であればC&Pを行い、調整すればすぐに使えるzlib/libpngライセンスなソースコードが含まれています。VS使用者には取り付きやすいサンプルです。


  • example.c - 実装前にどのようなlibpngの使い方について学習するのに使えそうです。

  • pngtest.c - pngの読み込み・書き込みテスト用コード。コンパイル可能。

  • libpng-manual.txt - libpngのマニュアルテキスト。各関数がどのような動作をするのかを調べたいときに使用できそうです。


  • contrib - このフォルダ内にはサンプルソースコードが数点含まれています。デコーダー・エンコーダーの実装例等も含まれているので研究用に使えそうです。


pngファイルの読み込み実装


 pngの高レベル関数を使用した画像の読み込みはそれほど難しくありません。
 pngの読み込み処理の流れは最低限かつ簡単にまとめると、下記のようになります。


  • pngのシグネチャ比較(png_sig_cmp)

  • png読込構造体を生成(png_create_read_struct)

  • png情報構造体を生成(png_create_info_struct)

  • pngファイルのファイルからの読み込み(png_init_io)

  • pngファイルフォーマットへ読込済みデータを変換(png_read_png)

  • pngファイルの画像データ部分の抽出(png_get_rows)

  • png処理の後始末(png_read_end等)


 順番に実行していくだけであれば簡単に見えますね。
 この間にエラー時処理・画像用メモリ領域の確保・高さ、幅、深度の計算等を含めていく必要があります。



png_init_ioとpng_set_read_fnはどちらを使用すれば?


 ドキュメントを意訳すると下記のようになるはず。超意訳なのでこんな文章がどこに書いてあるのかとか探さないでください。
png_init_ioは「Stardard C」で実装されており、内部処理としてfreadで実装されています。
そのため、freadを使用できる環境であるのであれば、png_init_ioを使用することが可能です。
組み込み環境等特殊な実行環境である場合、png_set_read_fnを使用して、ファイルIO処理を行ってください。


 Windows + VisualStudioが実行環境であるならば、png_init_ioを使用すれば問題ないでしょう。
 何らかの手法で高速に読み出したい場合や特殊な機器構成により低レベルなファイルIO処理を利用しないといけない場合などはpng_set_read_fnを使用して呼ばれる関数内で読み込み処理を実装するといいでしょう。
 例えば、libpngの中に含まれているexample.cの中で「Progressively read a file」という実装について書かれており、この実装はpng_set_read_fnに指定した関数内でpngの読み込み状況の進捗度合を算出しています。巨大なpngファイルが対象となっている場合には便利な処理です。

 不明点等コメントいただければ可能であれば補足します。

以上

2012/02/02

TortoiseSVNで外部参照(svn:externals)を使用する方法

Mercurial(hg)のsubreposみたいなことをSVNでもできたんですね……
これがあれば別々のプロジェクトでも共有ライブラリが使用できるから管理が楽です。

外部参照(svn:externals)設定手順


前提条件としてtortoiseSVNは次のバージョンを使用しています。
また、言語は英語のまま使用しているので、ところどころ日本語に置き換えてください。
TortoiseSVN 1.6.16, Build 21511 - 32 Bit , 2011/06/01 19:00:35
Subversion 1.6.17, 
apr 1.3.12
apr-utils 1.3.12
neon 0.29.6
OpenSSL 1.0.0d 8 Feb 2011
zlib 1.2.5

取り込みたいディレクトリでSVNメニューからプロパティを選択。
新規プロパティに下記のように設定。

[Property Name]=[svn:externals]
[Property Value]=[dirname http://path/to/Repos]


※[]の記号は入力不要なので注意。

例として、下記のような構成で外部参照リポジトリを設定する。
[root]
  ├[ProjectX]
  ├[SubProjectY]
  └[SUbProjectZ]

ここではrootフォルダの直下に別プロジェクトで使用しているcommonlibリポジトリを設置したいとする。
まずは[root]ディレクトリ上でプロパティを下記のように設定する。
commonlib http://svnserver-xxxx/svn/ProjectA/trunk/commonlib

この状態でUpdateを行うとpath/to/reposで設定したリポジトリからチェックアウトしてきます。
[root]
  ├[ProjectX]
  ├[SubProjectY]
  ├[SubProjectZ]
  └[commonlib] (svn:externals = http://svnserver-xxxx/svn/ProjectA/trunk/commonlib)

この状態でrootディレクトリをコミットしてやれば以降はこのリポジトリをアクセスした人も外部参照先からcommonlibをリポジトリにUpdateします。

注意したいことは、この外部参照リポジトリを変更し、コミットしたら参照先リポジトリ分も更新されます。読み取り専用にしたいのであれば、commonlibに対して、新しく[svn:needs-lock]プロパティを設けて共通ライブラリの編集はロックを行わないとできない形に変更してしまう方法が考えられます。

もしくは該当リポジトリ側の書き込み制限をユーザー・グループ毎に区切って、そもそも権限がないと変更できないようにしてしまうかという形でしょう。

ローカルのワークに対して変更しないようにしたいだけであれば、フォルダのプロパティ自体を読み取り専用にして外部参照リポジトリ直下のフォルダを書き込み禁止にするといいかもしれない。.svnフォルダも書き込めなくなるかもしれないので注意。

本文を用いて外部参照リポジトリの設定して、わからないこと、躓いたこと等があればコメントしていただかえれば可能な限り補足使用と思います。

以上