Oracle CorporationのMartin Backさんが、setTimeout()のpolyfillを作成し、画像処理ライブラリのJIMPをMLEにロードして、画像をグレースケールに変換する手順をブログ記事で公開されています。
Multilingual Engine: polyfill timeouts and intervals
JIMPについては以前にMLEにロードを試みたことがありますが、Martin Backさんの記事にあるようにMLEにはsetTimeout()やfsモジュールが無いため諦めました。
今回はMartin Backさんの記事に沿って、MLEにJIMPとsetTimeout()のpolyfillをロードし、それを使用して画像をグレースケールに変換するAPEXアプリケーションを作成します。JIMPのロードやpolyfillの説明については、上記に掲載したMartin Backさんのブログ記事を参照してください。
作成するAPEXアプリケーションは以下のように動作します。
以下より、実施した作業を記述します。
APEXワークスペースに紐づくスキーマで、MLEモジュールのロードやJavaScriptを実行します。そのため、スキーマにJavaScriptを実行する権限を与えます。これらのスクリプトはDBA権限を持つユーザー(Autonomous DatabaseであればADMIN、オンプレミスであればSYS AS SYSDBA)で実行します。
grant execute on javascript to [APEXワークスペース・スキーマ];
grant execute dynamic mle to [APEXワークスペース・スキーマ];
grant create mle to [APEXワークスペース・スキーマ];
続いて、作業に使用するディレクトリを作成し、プロジェクトを初期化します。ディレクトリ名はdemo-jimpとします。今回の作業で使用したnpmのバージョンは11.3.0です。
npm -v
mkdir demo-jimp
cd demo-jimp
npm init -y% npm -v
11.3.0
% mkdir demo-jimp
% cd demo-jimp
demo-jimp % npm init -y
Wrote to /Users/**********/Documents/demo-jimp/package.json:
{
"name": "demo-jimp",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs"
}
demo-jimp %
jimpをインストールします。
npm install jimp
demo-jimp % npm install jimp
added 70 packages, and audited 71 packages in 6s
10 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
demo-jimp %
esbuildをインストールします。demo-mle.jsに画像をグレースケールに変換するファンクションimage2Greyscaleを記述し、esbuildを実行してdemo-mle.jsにJIMPをバンドルします。
npm install --save-exact --save-dev esbuild
demo-jimp % npm install --save-exact --save-dev esbuild
added 26 packages, and audited 97 packages in 2s
10 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
demo-jimp %
Martin BackさんのGitHubのリポジトリより
demo-mle.jsと
polyfill.jsをダウンロードし、src/javascript以下に配置します。
curl -OL https://raw.githubusercontent.com/martincarstenbach/javascript-blogposts/refs/heads/main/timeout-polyfill/src/javascript/demo-mle.js
curl -OL https://raw.githubusercontent.com/martincarstenbach/javascript-blogposts/refs/heads/main/timeout-polyfill/src/javascript/polyfill.js
mkdir -p src/javascript
mv demo-mle.js polyfill.js src/javascript
ls src/javascript
demo-jimp % curl -OL https://raw.githubusercontent.com/martincarstenbach/javascript-blogposts/refs/heads/main/timeout-polyfill/src/javascript/demo-mle.js
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 1023 100 1023 0 0 4903 0 --:--:-- --:--:-- --:--:-- 4918
demo-jimp % curl -OL https://raw.githubusercontent.com/martincarstenbach/javascript-blogposts/refs/heads/main/timeout-polyfill/src/javascript/polyfill.js
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 2996 100 2996 0 0 7682 0 --:--:-- --:--:-- --:--:-- 7701
demo-jimp % mkdir -p src/javascript
demo-jimp % mv demo-mle.js polyfill.js src/javascript
demo-jimp % ls src/javascript
demo-mle.js polyfill.js
demo-jimp %
esbuildを実行しダウンロードしたdemo-mle.jsにJIMPをバンドルします。
npx esbuild src/javascript/demo-mle.js --bundle --outfile=dist/bundle.js --format=esm
demo-jimp % npx esbuild src/javascript/demo-mle.js --bundle --outfile=dist/bundle.js --format=esm
dist/bundle.js 1008.8kb
⚡ Done in 44ms
demo-jimp %
SQLclでAPEXのワークスペース・スキーマに接続し、MLEモジュールを作成します。
以下は、ローカルで実行しているOracle Database 23ai Freeに、ユーザーWKSP_APEXDEVで接続して作業した例になります。
sql wksp_apexdev/[パスワード]@localhost/freepdb1
demo-jimp % sql wksp_apexdev/********@localhost/freepdb1
SQLcl: 水 6月 25 12:31:27 2025のリリース25.1 Production
Copyright (c) 1982, 2025, Oracle. All rights reserved.
接続先:
Oracle Database 23ai Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free
Version 23.8.0.25.04
SQL>
demo-mle.jsにJIMPをバンドルしたdist/bundle.jsを、MLEモジュールJIMP_TEST_MODULEとして作成します。
mle create-module -replace -module-name jimp_test_module -filename dist/bundle.js
SQL> mle create-module -replace -module-name jimp_test_module -filename dist/bundle.js
MLEモジュールjimp_test_moduleが作成されました
SQL>
setTimeout()のpolyfillを、MLEモジュール
TIMERS_POLYFILLとして作成します。
mle create-module -replace -module-name timers_polyfill -filename src/javascript/polyfill.js
SQL> mle create-module -replace -module-name timers_polyfill -filename src/javascript/polyfill.js
MLEモジュールtimers_polyfillが作成されました
SQL>
以上で、demo-mle.jsに含まれているファンクションimage2Greyscaleを、データベース内で呼び出せるようになりました。
画像をグレースケールに変換するAPEXアプリケーションを作成します。
変換する画像および変換後の画像を保存する表EBAJ_DEMO_IMAGESを作成します。
SQLワークショップのSQLコマンドで、以下のDDLを実行します。
create table ebaj_demo_images (
id number generated by default on null as identity
constraint ebaj_demo_images_id_pk primary key,
filename varchar2(80 char),
mime_type varchar2(80 char),
operation varchar2(80 char),
source_image blob,
target_image blob
);
作成したMLEモジュールJIMP_TEST_MODULEおよびTIMERS_POLYFILLを含むMLE環境としてJIMP_ENVを作成します。以下のCREATE MLE ENV文を実行します。
create mle env jimp_env imports (
'jimp' module jimp_test_module,
'polyfill' module timers_polyfill
);
使用するデータベース・オブジェクトの準備ができました。
APEXアプリケーションを作成します。
アプリケーション・ビルダーよりアプリケーション作成ウィザードを開きます。アプリケーションの名前はdemo jimpとします。デフォルトで追加されているホーム・ページは削除します。
ホーム・ページを削除したのちページの追加をクリックし、代わりとなるフォームのページを追加します。
ページの追加をクリックしたときに開く画面でフォームを選択します。
ページ名はグレースケール変換とします。表に先ほど作成したEBAJ_DEMO_IMAGESを選択します。
ページの追加をクリックします。
表EBAJ_DEMO_IMAGESをソースとしたフォームのページが追加されました。
アプリケーションの作成をクリックします。
アプリケーションが作成されます。
画像のアップロードやグレースケールへの変換処理は、ページ番号1の
グレースケール変換に実装します。
APEXアプリケーションからMLEモジュールを参照できるように、APEXアプリケーションにMLE環境を紐づけます。
アプリケーション定義のセキュリティのデータベース・セッションのセクションに含まれるMLE環境に、先ほど作成したMLE環境JIMP_ENVを設定します。
変更の適用をクリックします。
ページ・デザイナでページ、グレースケール変換を開きます。
主キー値を保持するページ・アイテムP1_IDのセッション・ステートのストレージをセッションごと(永続)に変更します。
新規に画像がアップロードされる場合、ページ・アイテム
P1_IDには
空白(null)が渡されます。ページ送信時に実行されるプロセスでは、画像を含む行をデータベースに挿入する際に、INSERT INTO ... RETURNING id INTO :P1_ID という構文を用いて主キーの値を取得し、それを
P1_IDに設定します。
しかし、
セッション・ステートの
ストレージが
リクエストごと(メモリのみ)になっていると、プロセスの処理が完了してフォームのページが再描画される際に、
P1_IDの値がセッション・ステートに保存されておらず、結果として
P1_IDは空白のままになります。このため、直前にアップロードされた画像を
P1_IDの値を用いて参照できません。
通常、フォームはレポート画面から開かれ、フォームを閉じるとレポート画面に戻る構成であるため、このような問題は発生しません。
しかし、今回のアプリケーションでは、フォームを閉じた後に再び同じフォームに戻る動作となっているため、
P1_IDの値がページ遷移を超えて維持される必要があります。この対応として、
セッション・ステートの
ストレージを
セッションごと(永続)に変更します。
ページ・アイテムP1_FILENAME、P1_OPERATIONおよびP1_TARGET_IMAGEは非表示に変更します。
今回のAPEXアプリケーションでは使用しない属性ですが、表EBAJ_DEMO_IMAGESの列OPERATIONがつねにgreyscacleになるようにします。
ページ・アイテムP1_OPERATIONに計算を作成します。
実行のポイントとして送信後を選択し、計算のタイプに静的値、静的値としてgreyscaleを設定します。これでページ送信時のP1_OPERATIONの値がgreyscaleになります。
アップロードするファイルを選択するページ・アイテムP1_SOURCE_IMAGEのタイプをファイルのアップロードからイメージ・アップロードに変更します。
ストレージのMIMEタイプ列にMIME_TYPE、ファイル名列にFILENAMEを設定します。
リージョンEbaj Demo Imageの外観のテンプレートを装飾のないBlank with Attributesに変更します。
テンプレートが変更されたので、ボタンが配置されていたスロットが無くなっています。ボタンについては、Bodyの直下に横並びになるように移動します。
ボタンCANCELは削除し、CREATE、SAVE、DELETEの順に並べます。
すべてのボタンの外観のホットはオフにし、テンプレート・オプションのWidthをStretchに設定します。
ボタンDELETEのレイアウトの新規行の開始をオフにし、ボタンSAVEの右隣に表示させます。
グレースケール変換前の画像を表示するページ・アイテムとしてP1_SOURCEを作成します。
タイプはイメージの表示、ラベルはSource、設定の基準にSQL文で戻されたBLOB列を選択し、SQL文として以下を記述します。
select source_image, null, filename, mime_type
from ebaj_demo_images
where id = :P1_ID
同様にグレースケール変換後の画像を表示するページ・アイテムとしてP1_TARGETを作成します。
ラベルはTarget、SQL文は以下になります。変換前の画像の右隣に表示させるため、レイアウトの新規行の開始はオフにします。
select target_image, null, filename, mime_type
from ebaj_demo_images
where id = :P1_ID
以上でユーザー・インターフェースについては完成です。
本記事の本題であるJIMPによるグレースケール処理を実装します。
プロセスを作成し、プロセス・フォームグレースケール変換の下に配置します。
識別の名前はgreyscale、タイプはコードを実行とします。ソースの言語にJavaScript(MLE)を選択し、JavaScriptコードとして以下を記述します。
サーバー側の条件のタイプにリクエストは値に含まれるを選択し、値にCREATE SAVEを入力します。ボタンCREATEおよびSAVEをクリックしたときに、プロセスgreyscaleが実行されます。
ボタンDELETEをクリックするとフォームに表示されている行(P1_IDが主キー値である行)が削除されます。フォームに設定されている削除済みの行のデータをリセットするプロセスを作成します。
識別の名前はreset、タイプにセッション・ステートのクリアを選択し、設定のタイプとして現在のページのすべてのアイテムのクリアを選択します。
サーバー側の条件のボタン押下時はDELETEです。
以上でアプリケーションは完成です。
アプリケーションを実行すると記事の先頭のGIF動画のように動作します。
今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/demo-jimp-in-apex-app.zip
Oracle APEXのアプリケーション作成の参考になれば幸いです。
完