表示調整
閉じる
挿絵表示切替ボタン
▼配色
▼行間
▼文字サイズ
▼メニューバー
×閉じる

ブックマークに追加しました

設定
0/400
設定を保存しました
エラーが発生しました
※文字以内
ブックマークを解除しました。

エラーが発生しました。

エラーの原因がわからない場合はヘルプセンターをご確認ください。

ブックマーク機能を使うにはログインしてください。
11/19

ビット反転画像ビューアーアプリ

 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のプログラムのパッケージの名前です。

評価をするにはログインしてください。
ブックマークに追加
ブックマーク機能を使うにはログインしてください。
+注意+

特に記載なき場合、掲載されている作品はすべてフィクションであり実在の人物・団体等とは一切関係ありません。
特に記載なき場合、掲載されている作品の著作権は作者にあります(一部作品除く)。
作者以外の方による作品の引用を超える無断転載は禁止しており、行った場合、著作権法の違反となります。

この作品はリンクフリーです。ご自由にリンク(紹介)してください。
この作品はスマートフォン対応です。スマートフォンかパソコンかを自動で判別し、適切なページを表示します。

↑ページトップへ