yuusuke-roughの日記

Java,SpringBoot,趣味等

画像の圧縮とBase64の変換処理 in JavaScript

はじめに

前回と同様に日記形式で進めます。

仕事の空いている時間に学習した内容も併せて記載。

作成中の日記が長すぎるため、分割。

内容

前記事の公式ドキュメント参照

  • "clientInboundChannel" for messages from WebSocket clients.
  • "clientOutboundChannel" for messages to WebSocket clients.
  • "brokerChannel" for messages to the broker from within the application.

 

OutboundChannelとInboundChannelのスレッドが増え続ける事については、ハートビートのPING、PONG毎にスレッドが実行中のままである事が原因だった。

状況:チャット画面を表示した時点でInboundChannelが3つ、OutboundChannelが1つ、MessageBrokerが複数実行される。

スレッドは定期的に削除されている可能性が...仕組みがわからない。

力不足を感じる。ひとまず、機能実装後に考える。

 

-------------------------------------------------------------------------------------------------

画像送受信について、調べると下記のような情報があった。

javascript - Sending images/files over Sockjs + Spring Websocket + Stomp - Stack Overflow

 

流れとしては、クライアント側で画像をエンコードして、Spring Boot側でデコードし保存という流れだろうか。

MySQLの場合はBLOB型で保存という事なので、試してみる。

 

①HTMLに設置された画像をJavaScriptBase64に変換して、STOMP.jsでContentTypeを指定して送信

STOMP.js Documentation

第二引数でヘッダーの作成ができる。デフォルトはjson/applicationのため変換する。

 

拡張子について調べると、IPhoneやアンドロイドの画像はjpegとの事で、jpgかjpegで登録できるようにする。heifやheicにも対応できた方が良いとの事なので、上記4つの拡張子に対応することを考える。

 

heifやheic変換のJsライブラリ

Heic2any: Client-side conversion of HEIC/HEIF image files to JPEG, PNG, or GIF in the browser.

heic2any/getting-started.md at master · alexcorvi/heic2any · GitHub

ダウンロードでjsファイルが開かれるので保存。

 

圧縮はひとまず置いておいて、1.HTMLで画像を受け取る 2.Heic2anyで画像を変換し、ContentTypeを指定。 3.STOMP.jsにてContentType指定、画像を送信し、ChannelInterceptorで受信内容の確認。

 

JavaScriptの基礎と混ぜて進めていく。

まず、HTMLに画像をセットした後に拡張子がheicやheifの場合は、拡張子をjpegやjpgにして、誰でも利用できるようにしたい。

そのため、拡張子のみをJavaScriptで取得。

var fileExtension = document.getElementById("sendImage").value.split(".").pop();
    if(fileExtension == "jpeg" || fileExtension == "png" || fileExtension == "jpg" ){
    
    }
    
    if(fileExtension == "heic" || fileExtension == "heif"){
        fetch(document.getElementcById("sendImage"))
            .then*1
            .then((blob) =>
                    heic2any({
                                blob,
                                toType: "image/jpeg",
                                quality: 0.5,
                            })
            )
            .then((conversionResult) => {
                                  sendImage(conversionResult)
            })
    }

 

ひとまず、jpg、jpegの部分とsendImage()手前でBase64への変換が必要になる。

 

エスケープ処理とBase64へのエンコードについて

encodeURIComponent() - JavaScript | MDN

btoa() - Web API | MDN

 

最後は、STOMP.jsにて、ヘッダーをセットし送信処理を行う。

 

Error: The file given is not an instance of Blob or File
    at index.js:84:24
    at new Promise (<anonymous>)
    at imageCompression (web-worker.js:1:1)
    at addjustmentImage (app.js:60:33)
    at HTMLButtonElement.<anonymous> (app.js:111:37)
    at HTMLButtonElement.dispatch (jquery.min.js:2:43090)
    at v.handle (jquery.min.js:2:41074)

 

typeof(document.getElementById("sendImage").value)
'string'

.value→.files[0]にすると、

ReferenceError: image is not defined

STOMP.js内でheaderの値がおかしくなっていた。

"Content-Type":"image/jpeg"に設定

 

>>> SEND
Content-Type:image/jpeg
destination:/app/room/498894c2-a9e4-4b62-aaf0-4869e2f6b02d
content-length:90

{"message":"JTVCb2JqZWN0JTIwQmxvYiU1RA==","roomId":"498894c2-a9e4-4b62-aaf0-4869e2f6b02d"}

いや、これ中身画像じゃない...

 

  1. File {name: 'channelInterceptorでのヘッダー等の中身.png', lastModified: 1679812501797, lastModifiedDate: Sun Mar 26 2023 15:35:01 GMT+0900 (日本標準時), webkitRelativePath: '', size: 42723, …}
    1. lastModified: 1679812501797
    2. name: "channelInterceptorでのヘッダー等の中身.png"
    3. size: 42723
    4. type: "image/png"
    5. webkitRelativePath: ""

Fileでは受け取れている。

Promise付けたらと試すも同じ。(JavaScriptちゃんと勉強しないと...)

 

File - Web API | MDN

あくまでデータをJavascriptでアクセスできるようにするもの

 

FileReader - Web API | MDN

明示的に選択したファイルの内容にのみアクセスすることができます

...

指定された Blob の内容の読み込みを開始し、終了すると、result 属性にはファイルからの生のバイナリーデータが文字列として格納されます。

うまくいきました。

※FileReaderのonload()について、非同期処理のためonload()外に値を持ち込む事はできない。関数の外で呼び出そうとすると空が返ってくる

 

index.js:84 Uncaught (in promise) Error: The file given is not an instance of Blob or File
    at index.js:84:24
    at new Promise (<anonymous>)
    at imageCompression (web-worker.js:1:1)
    at reader.onload (app.js:67:30)

 

const compressedFile =  imageCompression(imageFile, options)

imageFileはFileかBlobでないとだめのため元のFileオブジェクトに修正。

 

  1. Blob {name: 'channelInterceptorでのヘッダー等の中身.png', lastModified: 1679812501797, size: 40699, type: 'image/jpeg'}
    1. lastModified: 1679812501797
    2. name: "channelInterceptorでのヘッダー等の中身.png"
    3. size: 40699
    4. type: "image/jpeg"

2000ほど圧縮されました。

 

Spring Bootにて

Cannot deserialize value of type `java.lang.String` from Object value (token `JsonToken.START_OBJECT`)

 

at [Source: (byte[])"{"message":{},"roomId":"498894c2-a9e4-4b62-aaf0-4869e2f6b02d"}"; line: 1, column: 12] (through reference chain: com.example.demo.model.discussion.ChatMessage["message"])

空になっている。

 

  1. Promise {<pending>}
    1. Prototype: Promise
      1. Symbol(Symbol.toStringTag): "Promise"
    2. PromiseState: "fulfilled"
    3. PromiseResult: undefined

Base64に変換後の値がまだPromiseになっていた。

プロミスの使用 - JavaScript | MDN

呼び出される順番を記載すると...

変換後の処理→変換のためのonload()と逆になっている。

つまり、onload()が非同期処理のため、変換後の処理は並行して行われてしまうが、まだ値自体が渡されていないため、空になるという事だ。

よって、変換処理をPromise内に置き、onload()内でresolveし、次のthenに渡すことで解決した。

 

org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]

com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: Data too long for column 'message' at row 1

 

原因は、宛先がメッセージと同じであったため。

また、クライアント側でも...

>>> SEND
Content-Type:image/jpeg
destination:/app/room/498894c2-a9e4-4b62-aaf0-4869e2f6b02d
content-length:54330

(以下、変換後の文字列)

...

 

remaining = 38057
stomp.min.js:8 remaining = 21673
stomp.min.js:8 remaining = 5289

 

実装予定

送信方法の指定

コントローラーでのBLOB変換

 

感想

Promiseや非同期処理について、程よく学べたのではないかと思います。

また、AtCoderでは、式変形やDPを社内の方々から教えてもらいました。

幸いな事に、仕事は暇なので、EffectiveJavaやDZone、MDNのJavaScriptの項あたりを読みつつ、PG職になるために備えたいと思います。

*1:res) => res.blob(