ビット反転画像ビューアーアプリ
Githubでビット反転画像ビューアーアプリの.apkファイルなどをパブリック ドメインで公開しております。
マイクロソフトのBing検索エンジンで「github eliphas1810-tools」などで検索してみてください。
残念ながらグーグル検索エンジンでは検索できません。
例えば、センシティブな画像や動画をクラウドにアップロードすると、グーグルのクラウドの場合はファイルを削除されたりアカウントを削除されたりするそうです。
パソコンで画像や動画を全ビット反転させてからアンドロイド スマホやタブレットにコピーすれば、画像や動画がアンドロイド システムに勝手にクラウドに保存されるのを予防できます。
著者の他の投稿作品の「Pythonによるツール」で1つ以上のファイルを全ビット反転させるプログラムのコードを、「JavaScript(とHTML)によるツール」で1つのファイルの全ビットを反転させるプログラムのコードを公開しております。
オフラインでスマホ内の.htmlファイルをChromeアプリで表示して.htmlファイル内のJavaScriptを起動して処理させる事ができるのですが、次のような操作手順が必要に成ってしまい面倒なので、ある.htmlファイル専用のアプリを自作しました。
①GoogleのFilesアプリなどでスマホ内の.htmlファイルを選択する。
②.htmlファイルを表示するアプリとしてChromeアプリなどを選択する必要が有ります。
③.htmlファイル内でファイル選択しようとすると、GoogleのFilesアプリ?などを選択する必要が有ります。
AQUOS sense3、Pixel7a、FireHD8第12世代2022年で動作を確認できました。
今の所、画像のサイズが大きくても、当アプリで表示できない画像は無いようです。
※下記のXMLファイルやKotlinのプログラムなどのコードをコピペする場合は、2文字の全角空白を4文字の半角空白に置換してください。
また、Android StudioにJavaやKotlinなどのプログラムのコードをコピペして、「import android.R」が自動で追加されてしまったら、削除してください。
「android.R」は、「R.layout.activity_main」や「R.id.◯◯◯」の「R」とは違います。
そのため、「import android.R」が有ると、コンパイル エラーが発生してしまいます。
Android StudioにJavaやKotlinなどのプログラムのコードをコピペすると、変数の名前が半角バッククォート記号(`)で囲まれる事が有ります。
Kotlinでは変数の名前を半角バッククォート記号(`)で囲むと予約語(inやnullなど)や半角空白記号( )などを変数の名前にできるそうです。
可能であれば、半角バッククォート記号(`)で囲まれた変数の名前は、半角バッククォート記号(`)で囲まずに済む名前に変更したほうが良いのでは、と個人的に思っております。
2025年2月6日に脆弱性CVE-2022-24329へ対応するため、build.gradleの依存ライブラリーを安全な新しいバージョンに更新しました。
/home/◯◯◯/AndroidStudioProjects/BitFlippedImageViewer/app/build.gradle
――――――――――――――――――――
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'eliphas1810.bitflippedimageviewer'
compileSdk 34
defaultConfig {
applicationId "eliphas1810.bitflippedimageviewer"
minSdk 24
targetSdk 34
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
――――――――――――――――――――
※build.gradleを変更したら必ずAndroid Studioで「Sync Now」をクリックして押してください。
※「Sync Now」をクリックして押して初めてAndroid Studioはbuild.gradleの変更を読み込んで必要なライブラリのjarファイルをダウンロードしてくれます。
①Android Studioで、アプリに同梱するファイルが置けるassetsディレクトリーを作成します。
②assetsディレクトリーにアプリへ同梱する.htmlファイルを置きます。
・次の.htmlファイルはオフラインのパソコンのChromeやFirefoxなどで表示して操作して処理させる事ができます。
・次の.htmlファイルでは、HTMLのinputタグによってファイルを選択できて、HTMLのbuttonタグによるボタンを押すと、選択したファイルを全ビット反転された画像ファイルと見なし、全ビット反転し直して元に戻してから、画像ファイルのバイナリー データをBase64形式のテキスト データに変換して、Base64形式のテキスト データの画像を表示するHTMLのimgタグを生成します。
/home/◯◯◯/AndroidStudioProjects/BitFlippedImageViewer/app/src/main/assets/BitFlippedImageViewer.html
――――――――――――――――――――
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>ビット反転画像ビューアー</title>
<style>
img {
width: 100%;
}
</style>
</head>
<body>
<div>
ビット反転画像ファイル選択 <input type="file" id="file" multiple />
</div>
<br />
<div>
<button type="button" id="showBitFlippedImage">ビット反転画像表示</button>
</div>
<br />
<p id="message"></p>
<br />
<div id="images"></div>
<script>
function $(id) {
return document.getElementById(id);
}
function readAsArrayBufferSync(file) {
return new Promise(function (resolve, reject) {
var fileReader = new FileReader();
fileReader.onload = function () { resolve(fileReader.result); };
fileReader.onerror = function () { reject(fileReader.error); };
fileReader.readAsArrayBuffer(file);
});
}
$("showBitFlippedImage").onclick = async function () {
var files = $("file").files;
if (files.length == 0) {
$("message").innerHTML = "ビット反転画像ファイルを選択してください。";
return;
}
$("message").innerHTML = "ビット反転画像ファイルを処理中です……。";
$("images").innerHTML = "";
for (var index = 0; index < files.length; index++) {
var file = files[index];
var arrayBuffer = await readAsArrayBufferSync(file);
var size = arrayBuffer.byteLength;
var dataView = new DataView(arrayBuffer);
//ビット反転してからJavaScriptの「バイナリー文字列」に変換
var jsBinaryString = "";
for (var byteIndex = 0; byteIndex < size; byteIndex++) {
jsBinaryString += String.fromCharCode(((~ dataView.getUint8(byteIndex)) >>> 0) & 0xff);
}
var divElement = document.createElement("div");
var imageElement = document.createElement("img");
var fileName = file.name;
if (fileName.match(/[^a-z]png[^a-z]/gi) != null) {
imageElement.src = "data:image/png;base64," + btoa(jsBinaryString);
} else {
imageElement.src = "data:image/jpeg;base64," + btoa(jsBinaryString);
}
divElement.appendChild(imageElement);
$("images").appendChild(divElement);
}
$("message").innerHTML = "";
};
</script>
</body>
</html>
――――――――――――――――――――
◯◯◯はLinux Mintのユーザー名です。
BitFlippedImageViewerは著者が付けたAndroid Studioのプロジェクトの名前です。
③アンドロイドのアプリの「ビュー」と呼ばれる画面の部品は、WebViewを除いて、そのままでは指のジェスチャーのピンチ アウトやピンチ インで拡大縮小できないので、既存の「ビュー」を継承して自作する必要が有ります。
・自作すると、ピンチ アウトやピンチ インを自分好みに滑らかにできます。
・Android Studioでは、「ビュー」を継承して自作すると、何もしなくても、画面の設定の.xmlファイルで利用できるように成ります。
・.htmlファイルを表示して操作したいので、拡大縮小できるWebViewを自作します。
・ScaleGestureDetector.scaleFactorで拡大縮小率を取得できますが、そのままでは過敏に反応して処理する羽目に成ってしまうので、直近の拡大縮小率を記憶して、0.05単位で拡大縮小率が変化した場合だけ、拡大縮小します。
・WebView.zoomIn()で拡大、WebView.zoomOut()で縮小できますが、本来よりも一定以下に小さく縮小できないようですし、拡大には限界が有るようです。
/home/◯◯◯/AndroidStudioProjects/BitFlippedImageViewer/app/src/main/java/eliphas1810/bitflippedimageviewer/ZoomableWebView.kt
――――――――――――――――――――
package eliphas1810.bitflippedimageviewer
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import android.webkit.WebView
class ZoomableWebView(
context: Context,
attributeSet: AttributeSet?,
defaultStyleAttribute: Int,
defaultStyleResourceId: Int
) : WebView(context, attributeSet, defaultStyleAttribute, defaultStyleResourceId), ScaleGestureDetector.OnScaleGestureListener {
private val scaleGestureDetector = ScaleGestureDetector(context, this)
private var lastScaleFactor = 1.0f
constructor(context: Context, attributeSet: AttributeSet?, defaultStyleAttribute: Int) : this(context, attributeSet, defaultStyleAttribute, 0)
constructor(context: Context, attributeSet: AttributeSet?) : this(context, attributeSet, 0, 0)
constructor(context: Context) : this(context, null, 0, 0)
override fun onTouchEvent(motionEvent: MotionEvent?): Boolean {
scaleGestureDetector.onTouchEvent(motionEvent!!)
return super.onTouchEvent(motionEvent)
}
override fun onScaleBegin(scaleGestureDetector: ScaleGestureDetector): Boolean {
return true
}
override fun onScale(scaleGestureDetector: ScaleGestureDetector): Boolean {
if ((lastScaleFactor / 0.05f).toInt() == (scaleGestureDetector.scaleFactor / 0.05f).toInt()) {
return true
}
lastScaleFactor = scaleGestureDetector.scaleFactor
//ピンチアウトの場合
//
//拡大の場合
//
if (1.0f < scaleGestureDetector.scaleFactor) {
zoomIn()
//ピンチインの場合
//
//縮小の場合
//
} else {
zoomOut()
}
return true
}
override fun onScaleEnd(scaleGestureDetector: ScaleGestureDetector) {
}
}
――――――――――――――――――――
◯◯◯はLinux Mintのユーザー名です。
BitFlippedImageViewerは著者が付けたAndroid Studioのプロジェクトの名前です。
eliphas1810/bitflippedimageviewerは著者が付けたJavaやKotlinのプログラムのパッケージのディレクトリの相対パスです。
eliphas1810.bitflippedimageviewerは著者が付けたJavaやKotlinのプログラムのパッケージの名前です。
④WebViewが有る画面の設定の.xmlファイルを作成して、「<WebView」を「<eliphas1810.bitflippedimageviewer.ZoomableWebView」に変更します。
/home/◯◯◯/AndroidStudioProjects/BitFlippedImageViewer/app/src/main/res/layout/activity_main.xml
――――――――――――――――――――
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<eliphas1810.bitflippedimageviewer.ZoomableWebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
――――――――――――――――――――
◯◯◯はLinux Mintのユーザー名です。
BitFlippedImageViewerは著者が付けたAndroid Studioのプロジェクトの名前です。
⑤画面に1対1対応しているアクティビティを用意します。
・assetsディレクトリーに置いた.htmlファイルをwebView.loadUrl()は「file:///android_asset/◯◯◯.html」という形式のURIで読み込む事ができるそうです。
・Kotlin側で、HTMLのinputタグによるファイル選択のイベントを受け取って、アンドロイドのSAF(ストレージ アクセス フレームワーク)などでファイル選択をさせて、ファイル選択結果をHTMLのJavaScriptへ戻す必要が有るようです。
/home/◯◯◯/AndroidStudioProjects/BitFlippedImageViewer/app/src/main/java/eliphas1810/bitflippedimageviewer/MainActivity.kt
――――――――――――――――――――
package eliphas1810.bitflippedimageviewer
import android.content.Intent
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.webkit.ValueCallback
import android.webkit.WebChromeClient
import android.webkit.WebView
import android.widget.*
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
class MainActivity : AppCompatActivity() {
private var valueCallback: ValueCallback<Array<Uri>>? = null
private var readActivityResultLauncher: ActivityResultLauncher<Intent>? = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult: ActivityResult ->
if (activityResult.resultCode == RESULT_OK) {
val intent = activityResult.data
var uriList = mutableListOf<Uri>()
//2つ以上のファイルが選択された場合
if (intent?.clipData?.itemCount != null) {
for (index in 0..(intent?.clipData?.itemCount!! - 1)) {
val uri = intent?.clipData?.getItemAt(index)?.uri as Uri
uriList.add(uri!!)
}
//1つ以下のファイルが選択された場合
} else {
if (intent?.data != null) {
val uri = intent?.data as Uri
uriList.add(uri!!)
}
}
valueCallback?.onReceiveValue(uriList.toTypedArray())
}
if (activityResult.resultCode == RESULT_CANCELED) {
var uriList = mutableListOf<Uri>()
valueCallback?.onReceiveValue(uriList.toTypedArray())
}
}
override fun onCreate(savedInstanceState: Bundle?) {
try {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val webView = findViewById<ZoomableWebView>(R.id.webView)
webView?.settings?.javaScriptEnabled = true //JavaScriptを有効化。デフォルトは無効。
webView?.settings?.allowFileAccess = true //ファイル アクセスを有効化。デフォルトは無効。
webView?.webChromeClient = object : WebChromeClient() {
override fun onShowFileChooser(
webView: WebView?,
filePathCallback: ValueCallback<Array<Uri>>?,
fileChooserParams: FileChooserParams?
): Boolean {
valueCallback = filePathCallback
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "*/*"
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
readActivityResultLauncher?.launch(intent)
return true
}
}
webView?.loadUrl("file:///android_asset/BitFlippedImageViewer.html")
} catch (exception: Exception) {
Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()
throw exception
}
}
override fun onDestroy() {
try {
valueCallback = null
readActivityResultLauncher?.unregister()
readActivityResultLauncher = null
} catch (exception: Exception) {
Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()
throw exception
} finally {
super.onDestroy()
}
}
}
――――――――――――――――――――
◯◯◯はLinux Mintのユーザー名です。
BitFlippedImageViewerは著者が付けたAndroid Studioのプロジェクトの名前です。
eliphas1810/bitflippedimageviewerは著者が付けたJavaやKotlinのプログラムのパッケージのディレクトリの相対パスです。
eliphas1810.bitflippedimageviewerは著者が付けたJavaやKotlinのプログラムのパッケージの名前です。




