YUV⇒RGB変換

前回、AndroidでOpenGL ESを使用してYUVデータの表示を行いました。

今回はブラウザ上にYUV⇒RGB変換した画像を表示したいと思います。

現在、主要なブラウザでは、JavaScriptのAPIとしてOpenGL ESの派生規格であるWebGLをサポートしています。

今回、HTML5で追加された<canvas>要素に対し、ローカルファイルから読み込んだYUVデータをRGB変換して表示していきます。
最初は、YUV⇒RGB変換後の画像を2Dのレンダラーを使用して描画してみます。
その後、WebGLを使用した描画を行ってみます。
最後にWebGLとGLSL ESを使用し、GPUでYUV⇒RGB変換を行って画像を表示します。

なお、言語に関してはJavaScriptでも問題ないのですが、私としては型指定ができる方がしっくりと馴染むので、TypeScriptを用いたいと思います。

TypeScriptの開発環境

先ず、TypeScriptの開発環境を準備します。
詳細は他のホームページに任せますが、今回は以下の開発環境を使用します。

Visual Studio Code

IDE(開発環境)としてはVisual Studio Code(以下VSCode)を使用します。
こちらのページからダウンロードできます。

インストールについては

VSCode インストール

で検索すれば色々と出てきますのでそちらを参考にして下さい。

Live Server

現在のVSCodeでは、特に他のツール等を導入しなくても、単体でHTMLやJavaScriptの確認やデバッグを行うことができます。

一方、簡易なサーバーを立ち上げてHTMLやJavaScriptの確認やデバッグができれば、更に便利かと思います。
そんな場合に便利なのがVSCodeの拡張機能のLive Serverです。

使用方法等については、

Live Server

で検索すれば色々と出てきますのでそちらを参考にして下さい。

Node.js

TypeScriptのコンパイラー等の開発環境をVSCodeで使用する場合、Node.jsが必要となります。

WindowsではNode.jsのホームページから最新のインストーラーをダウンロードし、実行するだけでインストールできます。

その他のプラットホームでのインストールはNode.jsのダウンロードページにリンクがありますので、参照して下さい。

プロジェクト作成

開発環境が整いましたら、プロジェクトを作成して行きます。
今回はNode.jsを使用してプロジェクトを構築します。

先ず、プロジェクトを作成したいフォルダーを適当に作成して下さい。
例えば”ShowYUVwithWebGL”と云うフォルダーを作成し、プロジェクトのフォルダーとします。

VSCodeをオープンします。
“ファイル”メニューから”フォルダーを開く…“を選択し、作成したプロジェクトのフォルダーを選択します。
VSCodeの新しいウィンドウがオープンします。

TypeScriptのコンパイラーのインストール

TypeScriptのコンパイラーのインストールの方法は色々とあるようですが、今回はNode.jsの”npm”コマンドでインストールします。

VSCodeで”Ctrl-@“等でターミナルをオープンします。
ターミナルで以下のコマンドによりTypeScriptのコンパイラーをインストールします。

npm install -g typescript@latest

なお、“-g”オプションは、グローバルにインストールすることを示し、他のプロジェクトでもTypeScriptのコンパイラーを使用できるようにします。
“-g”オプションを使用しない場合は今回のプロジェクトでのみTypeScriptのコンパイラーを使用できるようになります。

TypeScriptのバージョン確認

以下のコマンドでTypeScriptのバージョンを確認できます。

tsc -v
    

もしバージョンが想定しているより低い、例えば”1.0.0”等のような場合には、デフォルトで古いバージョンのTypeScriptがインストールされている可能性があります。
環境変数の”Path”に”C:Files (x86)SDKs\1.0.x.x”等が記載されていないか確認して下さい。
もし記載がある場合には”Path”から削除してみて下さい。

tsconfig.jsonの作成と修正

以下のコマンドによりTypeScriptのコンパイルのデフォルトの設定ファイル”tsconfig.json”が作成されます。

tsc --init

デフォルトの設定ファイルに対しては、以下の修正、追加を行います。

targetを変更

“target”の項目を”ES6”に変更します。

"target": "ES6",
      
moduleを変更

“module”の項目を”commonjs”から”ES2015”に変更します。

"module": "ES2015",
ベースURL指定

インポート文で絶対パス指定をできるようにベースディレクトリを指定します。

"baseUrl": "./src",
        
入力及び出力ディレクトリの追加

ソース元のディレクトリとコンパイル後のファイルを出力するディレクトリを追加します。
以下の項目の行のコメントアウトを外し、値を修正します。
なお、ディレクトリ名は適宜変更してもOKです。

"rootDir": "./src",
"outDir": "./dst/js",
        

更に、此処で指定したディレクトリに対応したフォルダをプロジェクトフォルダ”ShowYUVwithWebGL”下に作成しておきます。

階層化したコンパイル対象の指定

階層化したファルダをコンパイルの対象とするため、“include”の項目を以下のように追加します。

"compilerOptions": {
    :
    :
  },
"include": ["src/**/*"]
        
JavaScriptを対象

念のため、JavaScriptファイルを対象とするように以下の項目のコメントアウトを外しておきます。

"allowJs": true,
sourceMapの追加

デバッグ用のソースマップを作成するため以下の項目のコメントアウトを外しておきます。
リリースする際には再度コメントアウトしてソースマップを作成しない様にします。

"sourceMap": true,
        

プロジェクトの初期化

“Ctrl-@”等でオープンしたターミナルで以下のコマンドを実行し、プロジェクトを初期化します。

npm init -y

作成された”package.json”については、以下のように修正を行います。

スクリプト修正

“package.json”の”scripts”の値を以下のように修正し、次に導入するwebpackのコマンドをNPMスクリプトとして登録します。

"scripts": {
  "build": "webpack",
  "build:watch": "webpack -w",
  "node_modules": "npm ci"
},
    

なお”package-lock.json”に従ってnode_modules内のパッケージを再インストールするためのコマンドも追加しています。

webpackの導入

今回、TypeScriptのコードを書くのですが、クラス毎にファイルを分割したいと思います。
ただ、コンパイル後に作成される、複数のJavaScriptのファイルをHTMLで読み込み、動作させるには、色々と面倒な手続きがあり、容易ではありません。

webpackは、TypeScriptのファイルから作成された、分割されているJavaScriptのファイルを1つに纏め、HTMLから読み込めるようにしてくれるツールです。
他にも機能はあるのですが、詳細は他のホームページに任せます。

インストールは”Ctrl-@“等でオープンしたターミナル上で以下のコマンドを使用します。

npm install --save-dev webpack webpack-cli ts-loader

webpack.config.jsの作成

以下の内容でwebpackの設定ファイル”webpack.config.js”を作成します。

const path = require('path');
module.exports = {
    mode: 'development',
    entry: {
        index: './src/index.ts',
    },
    output: {
      path: path.join(__dirname, 'dst/js'),
      filename: 'index.js',
    },
    resolve: {
      extensions: ['.ts', '.js'],
    },
    devServer: {
        static: {
            directory: path.join(__dirname, 'dst'),
        },
        open: true,
    },
    module: {
        rules: [
            {
            test: /\.ts$/,
            loader: 'ts-loader',
            },
        ],
    },
    target: 'electron-main',
};
    

YUV⇒RGB変換

前回、OpenGLを使用してYUVデータを表示しました。
その際、プログラム言語としてOpenGLと相性の良いC/C++C#を使用しました。

今回はOpenGLを使用してYUVデータを表示するためにプログラム言語としてJavaを使ってみようかと思います。

ただ、Javaから直接OpenGLのAPIをコールする事はできませんので、実際にはJNIを通してC/C++等のネイティブコードからOpenGLのAPIをコールする事になります。
ですのでJavaでOpenGLを使用するのは、かなりのオーバーヘッドを伴います。

また現在のJavaのGUIフレームワークではDirectXやOpenGLを使用して描画しているものが殆どですので、単純に描画を行うだけであれば態々OpenGLを使わなくても、GUIフレームワークをそのまま使用した方が良いでしょう。

JavaでOpenGLを使うメリットはGLSLを使用してGPU側でYUV⇒RGB変換等の重い計算を行わせる際に発揮されます。

以下、普通にJavaを使用してYUVファイルを読み込んで画像を表示する事からOpenGLとGLSLを使用してGPUでYUV⇒RGB変換を行って画像を表示するまでをステップバイステップで説明していきます。

Javaの開発環境

先ず、Javaの開発環境を準備します。
詳細は他の方の説明に任せますが、必要な物は以下の通りです。

JDK

Javaのプログラムを作成するためにはJDKが必要です。
最新版はOracleのページからダウンロードできます。
因みにJREとは異なりますので注意して下さい。

インストール方法についてはOracleのページに"Installation Instructions"へのリンクがありますので、そちらを参考にして下さい。

更に

JDK インストール

で検索すれば、色々と出てきますのでそちらを参考にして下さい。

Visual Studio Code

IDE開発環境としてはVisual Studio Code(以下VSCode)を使用します。
こちらのページからダウンロードできます。

インストールについては

VSCode インストール

で検索すれば、色々と出てきますのでそちらを参考にして下さい。

なお、JavaのIDEとしてはEclipseNetBeans等が有名ですが、今回はVSCodeを使用します。

Java向け拡張機能の追加

VSCodeでJavaの開発を行うためには、Java向けの拡張機能Java Extension Packを導入すると便利です。
と云うか必須です。

追加方法は

Java VScode 拡張機能

で検索すれば、色々と出てきますのでそちらを参考にして下さい。

Apache Maven

今回、OpenGLのJava向けラッパーのライブラリを使用しますが、その管理をJava用プロジェクト管理ツールであるApache Mavenを使用します。

ライブラリの管理等はJava Extension Packを導入した際に同時に導入される拡張機能Maven for Javaでできるようなのですが、Apache Maven本体をインストールすると全ての機能を使用できるようになります。

ダウンロードやインストールについてはApache Mavenのホームページが詳しいので、そちらを参考にして下さい。

とりあえずウィンドウを表示してみる

プロジェクト作成

VSCodeで”コマンドパレット”(Ctrl+Shift+P)をオープンします。
コマンドパレットでは以下のように選択、入力します。

  1. “Java: Create Java Project…”を選択Javaプロジェクトの作成
  2. “Maven create from archetype”を選択Mavenを選択
  3. “maven-archetype-quickstart”を選択Maven Archetype quickstartを選択
  4. “Select version of maven-archetype-quickstart”で1.4を選択Maven Versionを選択
  5. “Input group Id of your project.”で”com.example”を入力Group IDを入力
  6. “Input artifact of your project.で”showyuv-jogl”を入力Artifact IDを入力

なお”group Id”び”artifact Id”については適当でOKです。
ターミナル上でMavenの設定が始まり、以下の問い合わせがありますのでそのままリターンして下さい。Version1 Snapshot

  1. “Define value for property ‘version’ 1.0-SNAPSHOT:”でリターン
  2. “Y: :”でリターン

描画領域とボタンを作成

メインのクラス名変更とインターフェース追加

クラス名およびファイル名がAppとなっているのでShowYUVに変更します(別の名前でもOKです)。
なおウィンドウを表示するためjavax.swing.JFrameを継承します。
因みにJavaのGUIフレームワークにはAWTSwingJavaFX等がありますが使い易さと標準で組み込まれている事を考慮してGUIフレームワークはSwingを中心に使用して行きます。

更にボタンを使用するためjava.awt.event.ActionListenerインターフェースをインプリメントしておきます。
なおActionListenerインターフェースはvoid actionPerformed(ActionEvente)メソッドが必要なのでとりあえず空のメソッドを追加しておきます。

package com.example;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
/**
 * YUVファイルを表示
 *
 */
public class ShowYUV extends JFrame implements ActionListener {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
    @Override
    public void actionPerformed(ActionEvent e) {
        // TODO Auto-generated method stub
    }
}

ShowYUVクラスのコンストラクタ作成

ShowYUVクラスのコンストラクタを作成しておきます。
コンストラクタ内ではファイルオープンボタンとプレイボタン、描画領域を追加します。
なおボタンと描画領域は他のメソッドからアクセスできるようにインスタンス変数としておきます。

public class ShowYUV extends JFrame implements ActionListener {
   static final int CIF_WIDTH = 352;
   static final int CIF_HEIGHT = 288;
   static final String TITLE = "CheckJOGL";
   static final String FILE_NAME = "File: ";
   static final String FILE_OPEN = "File...";
   static final String PLAY = "Play";
   private JPanel panel;
   private JLabel fileNameLabel;
   private JButton fileOpenButton;
   private JButton playButton;
   public ShowYUV(String title) throws HeadlessException {
       super(title);
       /* 描画領域 */
       JPanel borderPanel = new JPanel();
       borderPanel.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
       panel = new JPanel();
       panel.setPreferredSize(new Dimension(CIF_WIDTH, CIF_HEIGHT));
       borderPanel.add(panel);
       add(borderPanel);
       /* ボタン領域 */
       JPanel buttonPanel = new JPanel();
       buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
       buttonPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
       fileNameLabel = new JLabel(FILE_NAME);
       fileOpenButton = new JButton(FILE_OPEN);
       playButton = new JButton(PLAY);
       playButton.setEnabled(false);
       fileOpenButton.addActionListener(this);
       playButton.addActionListener(this);
       buttonPanel.add(fileNameLabel);
       buttonPanel.add(Box.createGlue());
       buttonPanel.add(fileOpenButton);
       buttonPanel.add(Box.createHorizontalStrut(5));
       buttonPanel.add(playButton);
       add(buttonPanel, BorderLayout.SOUTH);
       pack();
       setResizable(false);
       setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
       setLocationRelativeTo(null);
   }
       :
       :
}
とりあえずの描画領域の準備

描画領域はとりあえずJPanelクラスとしておきます(後で独自のクラスに置き換えます)。
なお描画領域”panel”をJPanelクラスの”borderPanel”の中に置いているのは単純に枠線を付けたいためなので枠線が必要ない場合にはJPanelを二重にする必要はありません。

描画領域のサイズは最終的にCIFサイズのYUVデータを描画するため(352, 288)にセットします。
因みにJPanelクラスのsetPreferredSizeメソッドで使用されているDimensionクラスはjava.awt.Dimensionなので注意が必要です。
他のフレームワークに同じ名前のクラスが存在するためIDEの補完機能で間違って選択されてしまって”???“となる場合があります。

ボタン類の配置

ファイルオープンボタン”fileOpenButton”とプレイボタン”playButton”の他にファイル名を表示するテキストラベル”fileNameLabel”を下部に配置します。
先ずこれらのボタン類を入れるコンテナJPanelクラスの”buttonPanel”をBoxレイアウトで準備します。
なおボタン類の周囲に余白を持たせるために”buttonPanel”に枠線を設定しておきます。
その中に左から”fileNameLabel”、“fileOpenButton”、“playButton”の順に配置して行きます。
各コンポーネントの間は適当にパディングを追加します。

コンポーネント配置後は”buttonPanel”をウィンドウの下部に配置しておきます。

メインウィンドウの諸設定

コンストラクタの最後ではウインドウの諸設定を行います。
先ずpack()メソッドでウィンドウのサイズを各コンポーネントの配置等に合わせて調整します。
次に描画するYUV画像のサイズが決まっているので”setResizable”メソッドでリサイズ出来ないようにしておきます。
更にウィンドウをクローズした時にアプリケーションを終了するように”setDefaultCloseOperation”メソッドで設定しておきます。

main関数でメインウィンドウの作成、表示設定

main関数ではShowYUVクラスのインスタンスを作成後setVisibleメソッドでウィンドウを表示します。

static final String TITLE = "ShowYUV";
public static void main(String[] args) {
    ShowYUV frame = new ShowYUV(TITLE);
    frame.setVisible(true);
}

この時点で実行すると、とりあえずのウィンドウを表示する事ができます。初期画面

ここまでのコードについてはGitHubからダウンロードできます。