enumにはCのunion的な使い方とCのenum的な使い方の両方があるということが分かったが、これを混ぜて使うことも多い。
// 色を表現する構造体 RGB #[derive(Copy,Clone,PartialEq)] struct RGB{ r:i32,g:i32,b:i32 } impl RGB{ fn New(R:i32,G:i32,B:i32)->RGB{return RGB{r:R,g:G,b:B};} } ////////////////////////////////////////////////// // 色を表現する構造体 HSV #[derive(Copy,Clone,PartialEq)] struct HSV{ h:f32,s:f32,v:f32 } impl HSV{ fn New(H:f32,S:f32,V:f32)->HSV{return HSV{h:H,s:S,v:V};} }
//////////////////////////////////////////////////
// 画像のピクセルを入れる型 // RGBの任意の値、HSVの任意の値 に加え、定数Invalid のどれかを入れることができる // Invalid(i32) と書き直すと少し理解がし易いが、入るのはInvalid一つだけなので意味的には違う // (概念的にはInvalidがたまたまi32にキャストできるだけで、i32を入れられるわけではないので) #[derive(Copy,Clone,PartialEq)] enum PixelInfo{ ColorRGB(RGB), // RGBを入れる場合 RGB型の変数ColorRGB ColorHSV(HSV), // HSVを入れる場合 HSV型の変数ColorHSV Invalid, // 定数を定義 RGBが無効のときはこれを入れておく事 }
////////////////////////////////////////////////// fn main() { let mut pixel:PixelInfo; // ピクセルを使うかどうかを決める let unuse:bool = false; if unuse { // 使わないときはInvalidを入れておく pixel = PixelInfo::Invalid; } else{ // 色を指定 pixel = PixelInfo::ColorRGB( RGB::New( 255, 0, 0, ) ); } if pixel == PixelInfo::Invalid{ // ピクセルが無効なら対応した処理を行う println!("the pixel is invalid"); } else if let PixelInfo::ColorHSV(c) = pixel{ // ピクセルが有効ならパターンマッチでcを定義して色を取得 println!("color HSV = ({} {} {})",c.h,c.s,c.v); } else if let PixelInfo::ColorRGB(c) = pixel{ // ピクセルが有効ならパターンマッチでcを定義して色を取得 println!("color RGB = ({} {} {})",c.r,c.g,c.b); } }
C++で似たようなコードを書くと、たぶん以下のようになる。
#include <iostream> struct RGB {int r, g, b;}; struct HSV {float h,s,v; }; union PixelInfo { RGB rgb; HSV hsv; // この定数も取れるようにする enum { INVALID, }Invalid; }; int main() { PixelInfo pi; pi.rgb = { 255, 255, 255 }; // 他の物を入れると無効になる pi.Invalid = PixelInfo::INVALID; std::cout << pi.Invalid; }
ぴょんきち 氏が開発、公開しているCLaunch。
http://hp.vector.co.jp/authors/VA018351/claunch.html
どうやら2023/08/11に最新バージョン4.05がリリースされた模様。
http://hp.vector.co.jp/authors/VA018351/whatsnew.html
以前、VC++との相性がやたらと悪いという話をした
のだが、五日間ほどVC++2022,Win32APIの開発時に併用してみたところ、一度も問題が起こらなかったので、解決しているのかもしれない。元々再現性100%ではないので断言できないのだが、間違いないとすればこれは朗報。ありがとうございます。
Rustのenumは高機能なunionという話が前回で、それとは別に、C/C++のenumのような使い方もできる。
// Rust の enum は、C言語のenumのように特定の値を持つ定数のみを取れる型を定義するように使用できる。 // ただし、指定できるのは整数のみで、f32やstring等は不可 enum AsciiCode{ AsciiA = 65, AsciiB = 66, AsciiC = 67, } fn main() { // cu を定義。入れられるのはAsciiA,B,Cのいずれかのみ let mut cu:AsciiCode = AsciiCode::AsciiA; cu = AsciiCode::AsciiB; // この場合もパターンマッチで比較する if let AsciiCode::AsciiA=cu{ println!("AsciiA is used." ); } else{ println!("Not A." ); } // 数値を取り出すときは as をつける println!("cu is {}",cu as i32); }
なお、変数同士を比較するには、==を使うが、デフォルトでは実装されていないので、PartialEqを指定する。
// 変数同士を比較するときは、==を使うためにPartialEqを指定 #[derive(PartialEq)] enum AsciiCode{ AsciiA = 65, AsciiB = 66, AsciiC = 67, } fn main() { // cu を定義。入れられるのはAsciiA,B,Cのいずれかのみ let cu:AsciiCode = AsciiCode::AsciiA; let rs:AsciiCode=AsciiCode::AsciiB; // == で比較できる if rs == cu{ println!("same"); } else{ println!("not same"); } }
注意点として、この記法 enum TYPE{ NAME= 65, } で指定できるのは整数のみで、f32などは入れられない。どうもこの表記自体はenumの本来の使い方としてはややずれているようで、Cなどのほかの言語との歩調を合わせるのがこの表記法が可能な主な理由だという(話があった)。
なお、数値を指定しなくても使える。as i32すれば整数にもなるが、あまりこれを前提としたコードを書くべきとは思えない。
// 型も値も指定しないバリアントを持てる。 // ==を使うためにPartialEqを指定 // as i32をするとmoveされてしまうのでCopyも指定。CopyのためにCloneも指定。 #[derive(Copy,Clone,PartialEq)] enum AsciiCode{ AsciiA, AsciiB, AsciiC, } fn main() { println!("A as int {}",AsciiCode::AsciiA as i32); println!("B as int {}",AsciiCode::AsciiB as i32); println!("C as int {}",AsciiCode::AsciiC as i32); // cu,rs を定義。入れられるのはAsciiA,B,Cのいずれかのみ let cu:AsciiCode = AsciiCode::AsciiA; let rs:AsciiCode = AsciiCode::AsciiB; // == で比較した例 if rs == cu{ println!("same"); } else{ println!("not same {} vs {}",cu as i32,rs as i32); } }
Rustのenumには三つの使い方がある。
まずはC言語におけるunionに近い使い方。
以下ではC_Union_Like型を定義しているが、この変数はi32,f32,Stringの「いずれか一つ」を代入することができる。
// Rust の enum は、C言語のunionに近い使い方ができる。 // 以下ではivalue,fvalue,svalueの三つのバリアントを定義しているが、 // 一度に有効なのは一つだけ。 enum C_Union_Like{ ivalue(i32), fvalue(f32), svalue(String), }
fn main() { // C_Union_Likeのivalueにi32を入れる。 let mut cu:C_Union_Like = C_Union_Like::ivalue(33); // fvalueに入れたので、cuはf32を持つようになった cu = C_Union_Like::fvalue(7.5); // svalueに入れたので、cuはStringを持つようになった cu = C_Union_Like::svalue("hello".to_string()); // cuは三つのバリアントを持つが、一度に有効なのは一つだけ。 // cu がどの値を持っているかを知る方法は // パターンマッチングでマッチするかで判断する // cu が ivalue にマッチするなら、iiを定義し、ivalueの値を代入 if let C_Union_Like::ivalue(ii)=cu { println!("int is used. {}",ii); } else if let C_Union_Like::fvalue(ff)=cu{ println!("float is used. {}",ff); } else if let C_Union_Like::svalue(ss)=cu{ println!("String is used. {}",ss); } }
このプログラムは、C/C++における以下に近い。
struct C_Union_Like { int type; union { int ivalue; float fvalue; //std::string svalue; // これはC++ではできないが、Rustならできる }; }; int main() { C_Union_Like cu; cu.type = 0; cu.ivalue = 33; cu.type = 1; cu.fvalue = 7.5f; switch (cu.type) { case 0: printf("int is used. %d \n", cu.ivalue); break; case 1: printf("float is used. %d \n", cu.fvalue); break; } }
Rustのenumは、C/C++のunionと違ってStringなどの複雑な型を取ることができる。さらに型情報も自動で持つので、unionのように自分で管理する必要がない。注意点として、今何が入っているかはパターンマッチングで判断する。上ではif letを使っているが、matchを使ってもよい。
fn main() { // C_Union_Likeのivalueにi32を入れる。 let mut cu:C_Union_Like = C_Union_Like::ivalue(33); // svalueに入れたので、cuはStringを持つようになった cu = C_Union_Like::svalue("hello".to_string()); // fvalueに入れたので、cuはf32を持つようになった cu = C_Union_Like::fvalue(7.5); // cuは三つのバリアントを持つが、一度に有効なのは一つだけ。 // cu がどの値を持っているかを知る方法は // パターンマッチングでマッチするかで判断する // cuの取りうるバリアントが多い場合はmatchのほうが読みやすい match cu{ C_Union_Like::ivalue(ii) => { println!("int is used. {}",ii); }, C_Union_Like::fvalue(ff) => { println!("float is used. {}",ff); }, C_Union_Like::svalue(ss) => { println!("String is used. {}",ss); }, _ =>{} } }
if let文について。
端的にいうと、
fn main() { let mytuple=(3,4,5); // (3,4,何でもいい) というタプルと (3,4,5) というタプルはマッチするので // if節が実行される if let (3,4,_) = mytuple{ println!("mached "); } // (3 ,second, 何でもいい) というタプルと、(3,4,5)はマッチするので // secondが定義されて、mytuple.1 が代入される if let (3,second,_) = mytuple{ println!("second element is {} ",second); } // (4,second,何でもいい) というタプルは、(3,4,5)とマッチしないので、 // else 節へ行く if let (4,second,_) = mytuple{ println!("second element is {}",second); } else { println!("not matched"); } }
if let はパターンマッチで分岐するので、match文に近い。matchが複数のパターンと比較するが、比較の対象が一つだけの時は書きやすいようにif letが用意されている。
あと、if letには網羅性がない。matchは網羅性があるので、必要なパターンを陳列し終わったら、「そのほかのケース」として_ を用意し、match文に用意された選択肢のどれか一つに必ずマッチするように書かなければならないが、ifはそうではない。
fn main() { let mytuple=(3,4,5); // mytuple と (3,second,_) のマッチ match mytuple{ (3,second,_)=>println!("first = 3, second = {}",second), _ => (), } // mytuple と (3,second,_) のマッチ。 // 比較対象が一つだけならif letのほうが直観的 if let (3,second,_) = mytuple{ println!("first = 3, second = {}",second); } }
Rustは構文のレベルでパターンマッチングをする言語で、しかも至る所でそれが出てくるので、パターンマッチングを受け入れないとコードを読めるようにならない。
例えばmatch文。
fn main() { let x:i32=5; let y:i32=10; // 「(5 , 10) というパターン」 match (x,y){ // (5,10) の内容が 「(5,10)」 に一致する場合 (5,10)=> println!("Both x and y are 5 and 10, respectively."), // (5,10) の内容が 「(5,何でもいい)」 に一致する場合 (5,_)=> println!("x is 5, and y can be any value."), // (5,10) の内容が 「上記のパターン以外」 に一致する場合 _ => println!("Neither of the above patterns match x and y."), } print!("{} {}",x,y); }
これはなんとなくわかる。
ところがRustには「パターンにマッチするか」だけではなく、「マッチして、それが変数だったらそこに値を拘束する」という機能もある。
つまり以下は、
・「パターンマッチ」
・「value_of_yの変数宣言」
・「value_of_yへのyの代入」
が同時に行われている。
fn main() { let x:i32=5; let y:i32=7; // 「(5 , 10) というパターン」 match (x,y){ (5,10)=> println!("Both x and y are 5 and 10, respectively."), // パターンマッチングを行い、マッチした場合は値の拘束(変数の定義と代入)も行う (5,value_of_y )=> println!("x is 5, and y is {}",value_of_y), _ => println!("Neither of the above patterns match x and y."), } }
一事が万事この調子なので、変数を定義する機能だと思っていたletも、実はパターンマッチと、マッチした場合の値の拘束を同時に行う構文だということになる。
だからlet文で変数定義をするときに_ (ワイルドカード)が使えてしまう。
fn main() { // 例1 // (x,y)というパターンと(3,5)はマッチする // マッチするので、 // x に 3 を拘束 // y に 5 を拘束 // この二つを同時に行う。 let (x,y)=(3,5); // 例2 // (z,何でもいい) というパターンと (7,9) はマッチする // マッチするから 7 が z に 拘束される // _ はワイルドカードなので無視される。 let (z,_)=(7,9); println!("{} {} {} ",x,y,z); }
let文の=の右辺と左辺でパターンマッチを行い、拘束可能な部分は拘束する。拘束できないところは無視する。
こんなもの何に使うんだと思うのだが、ある構造体なりタプルかなりから値を取り出したい、しかし全部取り出す必要はないときに、不要な変数宣言を省略できるという利点がある。
fn main() { // 三つの三次元座標を用いて三角形を一枚定義 let triangle_points = [ (0,3,9), // 頂点1 (7,0,6), // 頂点2 (4,7,2) // 頂点3 ]; // この三角形を Z 平面に投影した二次元座標を取得したい for p in triangle_points{ // 三要素の p から前二つだけ取り出し、x,yに拘束 // 三つ目の要素は z に入れるべきだが、不要なので _ を指定して省略。つまり変数 z は定義しない。 let (x,y,_) = p; println!("{} {}",x,y); } }
Rust的には、「比較するものとされるものがマッチするならコンパイルが通る」わけで、当然構造体でも同じ理屈が通用する。
構造体の場合、無視の記号は .. を用いる。また構造体はフィールド名で値を選択するので、順番は気にしなくていい。
struct Point { x: i32, y: i32, z: i32, } fn main() { let point = Point { x: 0, y: 7, z: 9 }; // point からy座標だけ取り出したいので // y_coord変数を定義し、 point.y を拘束 // .. により、ほかのフィールドは無視する let Point { y:y_coord, .. } = point; // point.y が取れているか確認 println!("y value is {}",y_coord); }
avifファイルを入力するサンプルコードを書いた。
#pragma comment(lib, "avif.lib") // 4996無効 #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <avif/avif.h> #include <vector> void pnmP3_Write(const char* const fname, const int width, const int height, const unsigned char* const p); /////////////////////////////////////////////////// /////////////////////////////////////////////////// /////////////////////////////////////////////////// // libavif で RGBデータをavifデータに変換する
int main() { /////////////////////////////////////////////// // avifのデコーダ avifDecoder* decoder = avifDecoderCreate(); /////////////////////////////////////////////// FILE* f = fopen(R"(output.avif)", "rb"); // avifファイルを全て読み込む fseek(f, 0, SEEK_END); long fsize = ftell(f); fseek(f, 0, SEEK_SET); //same as rewind(f); void* fileBuffer = malloc(fsize); long bytesRead = (long)fread(fileBuffer, 1, fsize, f); /////////////////////////////////////////////// avifResult result; result = avifDecoderSetIOMemory(decoder, (uint8_t*)fileBuffer, fsize); result = avifDecoderParse(decoder); /////////////////////////////////////////////// // デコード結果を取得する printf("Parsed AVIF: %ux%u (%ubpc)\n", decoder->image->width, decoder->image->height, decoder->image->depth); /////////////////////////////////////////////// // avifは複数の画像を持つことができるので、画像一枚ごとにループする while (avifDecoderNextImage(decoder) == AVIF_RESULT_OK) { /////////////////////////////////////////////// avifRGBImage rgb; memset(&rgb, 0, sizeof(rgb)); avifRGBImageSetDefaults(&rgb, decoder->image); /////////////////////////////////////////////// avifRGBImageAllocatePixels(&rgb); avifImageYUVToRGB(decoder->image, &rgb); /////////////////////////////////////////////// if (rgb.depth <= 8) { uint8_t* firstPixel = (uint8_t*)rgb.pixels; // 1画素4byteで格納されているので、 以下で画素を取得 //r = firstPixel[i*4 + 0] //g = firstPixel[i*4 + 1] //b = firstPixel[i*4 + 2] pnmP3_Write("output.ppm", rgb.width, rgb.height, firstPixel);// ファイルに保存 } } return 0; }
//! @brief PPM(RGB各1byte,カラー,テキスト)を書き込む //! @param [in] fname ファイル名 //! @param [in] width 画像の幅 //! @param [in] height 画像の高さ //! @param [in] p 画像のメモリへのアドレス //! @details RGBRGBRGB....のメモリを渡すと、RGBテキストでファイル名fnameで書き込む void pnmP3_Write(const char* const fname, const int width, const int height, const unsigned char* const p) { // PPM ASCII FILE* fp = fopen(fname, "wb"); fprintf(fp, "P3\n%d %d\n%d\n", width, height, 255); size_t k = 0; for (size_t i = 0; i < (size_t)height; i++) { for (size_t j = 0; j < (size_t)width; j++) { fprintf(fp, "%d %d %d ", p[k * 4 + 0], p[k * 4 + 1], p[k * 4 + 2]); k++; } fprintf(fp, "\n"); } fclose(fp); }
avifを出力するサンプル。
#pragma comment(lib, "avif.lib") // 4996無効 #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <avif/avif.h> #include <vector> // RGBの画像データを作成 std::vector<uint8_t> create_image(int width, int height) { std::vector<uint8_t> image(width * height * 3); for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { int index = (y * width + x) * 3; float t = static_cast<float>(y) / (height - 1); uint8_t red = static_cast<uint8_t>(255 * (1.0 - t)); uint8_t green = static_cast<uint8_t>(255 * t); uint8_t blue = 0; image[index] = red; image[index + 1] = green; image[index + 2] = blue; } } return image; } /////////////////////////////////////////////////// /////////////////////////////////////////////////// /////////////////////////////////////////////////// // libavif で RGBデータをavifデータに変換する
int main() { std::vector<uint8_t> rgb_src; int width = 300; int height = 200; rgb_src = create_image(width, height); // RGBデータをエンコードする( YUVデータは入力しないという意味 ) avifBool encodeYUVDirectly = AVIF_FALSE; // 今回はRGBデータを変換するサンプルとする if (encodeYUVDirectly == AVIF_FALSE) { //////////////////////////////////////////// // 変換後のデータの設定を行う int yuvDepth = 8; avifImage* dst_image = avifImageCreate(width, height, yuvDepth, AVIF_PIXEL_FORMAT_YUV444); //////////////////////////////////////////// // 変換前データの設定を行う avifRGBImage src_Image; avifRGBImageSetDefaults(& src_Image, dst_image); src_Image.format = AVIF_RGB_FORMAT_RGB; src_Image.pixels = rgb_src.data(); src_Image.rowBytes = width * 3; // AVIFはYUV形式で表現するため、まずYUVデータに変換する avifResult result = avifImageRGBToYUV(dst_image, &src_Image); if (result != AVIF_RESULT_OK) { printf("Failed to convert RGB image to YUV: %s\n", avifResultToString(result)); } //////////////////////////////////////////// // YUV形式のデータからAVIFのデータを生成 avifEncoder* encoder = avifEncoderCreate(); uint32_t quality = 90; // AVIFエンコード品質(0〜100) encoder->minQuantizer = AVIF_QUANTIZER_WORST_QUALITY - (uint8_t)(quality * 2.55); encoder->maxQuantizer = encoder->minQuantizer; //////////////////////////////////////////// // エンコード avifRWData output = AVIF_DATA_EMPTY; result = avifEncoderWrite(encoder, dst_image, &output); //////////////////////////////////////////// // 作成した画像をファイルに出力 FILE* oFile = fopen("output.avif", "wb"); fwrite(output.data, 1, output.size, oFile); fclose(oFile); //////////////////////////////////////////// // 終了処理 if (dst_image) { avifImageDestroy(dst_image); } avifEncoderDestroy(encoder); if (output.data) { avifRWDataFree(&output); } } return 0; }
普通にCMakeする。インストールディレクトリなどを指定する。
この時、以下のWarningが出る。
Searchにaomを入れるとAVIF_CODEC_AOMの項目を簡単に見つけられるので、ここにチェックを入れてConfigureする。
するとAOM_INCLUDE_DIRとAOM_LIBRARYを指定できるので、ビルド済みのライブラリとincludeでぃれくとりを指定する。
あとはConfigure , Generate ,Open Project , ALL_BUILD , INSTALL でインストールまで行く。
極力開発環境に変更を加えずにaomをビルドする
上記したものはパスが通っている必要があるが、環境変数へ直接書き足すと今回のコンセプトに反するので、バッチファイルを作り、上記すべてが使えるコンソールを立ち上げることにする。
上記すべてをどこかのパスに展開し、例えば以下のようなbatファイルを作り実行すると、パスの通ったコンソールが起動する。
REM perl SET PATH=C:\Software\strawberry-perl-5.32.1.1-64bit-portable;%PATH% REM Git SET PATH=C:\Software\PortableGit\bin;%PATH% REM CMake-GUI SET PATH=C:\Software\cmake-3.25.0-rc2-windows-x86_64\bin;%PATH% REM nasm SET PATH=C:\Software\nasm-2.16.01;%PATH% REM strawberry-perl environment call portableshell.bat start cmd.exe
まず、作業ディレクトリへ移動
git cloneでソースをダウンロード
cmakeする。コンソールからでももちろんいいが個人的に使い慣れているcmake-guiを起動。
Configure → Generate → Open → ALLBUILD → INSTALL
ビルドにやや時間がかかるが、D:\myDevelop\aom-build\install に各種includeファイルとaom.libが生成される。
D:\myDevelop\aom-build ├─aom ... git cloneでダウンロードすると生成 ├─install ... INSTALL用に作成 │ ├─bin │ ├─include │ │ └─aom │ └─lib └─solution ... cmakeした結果格納用に作成