質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

WPF

Windows Presentation Foundation (WPF) は、魅力的な外観のユーザー エクスペリエンスを持つ Windows クライアント アプリケーションを作成するための次世代プレゼンテーション システムです

Q&A

解決済

4回答

13299閲覧

【C#】画像データをできるだけ圧縮して保存して高速にデコードするには

OXamarin

総合スコア59

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

WPF

Windows Presentation Foundation (WPF) は、魅力的な外観のユーザー エクスペリエンスを持つ Windows クライアント アプリケーションを作成するための次世代プレゼンテーション システムです

0グッド

0クリップ

投稿2019/08/30 23:44

編集2019/08/31 02:24

##前提
環境:VS2019
言語:C# WPF

##最終的に達成したい事
リッチテキストボックスに、より多くの画像を高速に表示したい。

##悩んでいる事
以前、以下のような質問をしました。
【C#】【WPF】リッチテキストボックスに貼り付けた画像をバイナリーに保存して次回起動した際に表示したい

自己解決したのですが、ある程度運用していて気付いたのが、FlowDocumentをそのままバイナリにするととてつもなく重いという問題に直面しました。なんてことのない画像1枚を貼った場合でも、5MB程になっています。

この問題を解消する為に、画像のみをバイナリ化し、アプリケーション起動時にFlowDocumentを構築するようにしたいと思いました。

現状、以下のような方法で画像をバイナリ化しています。

C#

1/// <summary> 2/// BitmapSourceをbyte[]型へ変換 3/// </summary> 4/// <param name="bs"></param> 5/// <returns></returns> 6public static byte[] ToBinary(this BitmapSource bs) 7{ 8 if (bs == null) return null; 9 using (var ms = new MemoryStream()) 10 { 11 var encoder = new PngBitmapEncoder(); 12 encoder.Frames.Add(BitmapFrame.Create(bs)); 13 encoder.Save(ms); 14 ms.Seek(0, SeekOrigin.Begin); 15 return ms.GetBuffer(); 16 } 17}

85KBのPNGをこのメソッドを通してバイナリ化すると129KBになりました。
目標値などはありませんが、これ以上に圧縮する事は可能でしょうか?

エンコードは以下の様なコードです。
これだとメモリリークすると、どこかで拝見したことあります。タスクマネージャーでメモリをみる限りだとそんな事はないように見えていますが、より高速に、メモリリークのない正しいデコードの仕方などはありますでしょうか。

C#

1/// <summary> 2/// byte[]型をBitmapSourceへ変換 3/// </summary> 4/// <param name="byteArray"></param> 5/// <returns></returns> 6public static BitmapSource ToBitmapSource(this byte[] byteArray) 7{ 8 using (var ms = new MemoryStream(byteArray)) 9 { 10 var image = new BitmapImage(); 11 12 ms.Seek(0, SeekOrigin.Begin); 13 image.BeginInit(); 14 //当初はアイコンのみを扱うつもりだったが様々なサイズの画像を扱うためコメントアウト 15 //image.DecodePixelHeight = 16; 16 //image.DecodePixelWidth = 16; 17 image.CacheOption = BitmapCacheOption.OnLoad; 18 image.CreateOptions = BitmapCreateOptions.PreservePixelFormat; 19 image.StreamSource = ms; 20 image.EndInit(); 21 image.Freeze(); 22 return image; 23 } 24}

##試してみた事

  • PNGとJPEGでは当然、JPEGの方が軽いでしょうと思い、JpenBitmapEncoderを使用してみましたが増加してしまい、257KBとなりました。

  • PngBitmapEncoderにはありませんが、JpenBitmapEncoderにはQualityLevelプロパティがあり、最低品質の1に設定すると、20KBになりました。デコードして確認してみたところ、違和感はないようですが、やはり品質を落とすぐらいしかないのでしょうか。。

  • バイナリをBase64に変換して使用する文字数を減らすことで圧縮率が上がるのでは?と考えましたが、171KBに増えました。(1.33倍になるのは本当でした)

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

ベストアンサー

最終目的に対してはYAmaGNZさんの回答のとおりだと思います。
(ただ高圧縮なものほど展開に時間が掛かるというのは、様々な圧縮形式を総合しての大まかな傾向としてはそうでしょうけれど、個々の圧縮形式によって変わってくるので一概には言えません)

他の細かい点についていろいろ。

85KBのPNGをこのメソッドを通してバイナリ化すると129KBになりました。

.NETのPNGエンコーダーはあまり性能がよくないと見え、しばしば他で作ったPNGよりサイズが増えますね。

これ以上に圧縮する事は可能でしょうか?

他の性能の良いPNGエンコーダーを探してきて使うことで可能は可能でしょうけれど、1.5倍程度にそこまで労力をかけるべきかは疑問です。

PNGとJPEGでは当然、JPEGの方が軽いでしょうと思い

そのようなことはありません。PNGと(まともな画質の)JPEGのファイルサイズを比べたとき、単純な画像ではPNGが軽く、複雑な画像ではJPEGが軽いです。
ただ、JPEGは画質を落とせば(見るに堪えない画質になりつつも)軽くすることができます。
また、PNGはPNGに向かない画像で極端に重くなるのに対し、JPEGはどんな画像でも比較的一定したサイズを保つ特徴があります。

最低品質の1に設定すると、~、違和感はないようですが

本当でしょうか。大抵の画像はJPEGの最低品質では極端にノイズが多く見るに堪えない画質になるものですが。
よろしければその画像を貼っていただきたいです。

これだとメモリリークすると、どこかで拝見したことあります

私は詳しくないので分かりませんでした。その「どこか」の記述があると分かるかもしれません。

投稿2019/08/31 04:18

ikadzuchi

総合スコア3047

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

OXamarin

2019/08/31 06:04

ありがとうございます。 普通にやるとこんなものなのか?と思っていた部分が多くありましたので安心(?)しました。 >他の性能の良いPNGエンコーダーを探してきて使うことで可能は可能でしょうけれど、1.5倍程度にそこまで労力をかけるべきかは疑問です。 代替のエンコーダーに心当たりはありますか?もしあれば教えて頂きたいです。 >大抵の画像はJPEGの最低品質では極端にノイズが多く見るに堪えない画質になるものですが。 これは私の勘違いでした。Quality = 1 でエンコードした結果を正しくデコードできていませんでした。仰る通りにノイズがひどすぎます。Quality = 50 で PngEncoderでエンコードした場合と同じ重さになりましたが、画質を見る限り、それならPngでいいやと結論に至りました。 >その「どこか」の記述があると分かるかもしれません。 どこか、ではありませんが似たような記事がありました。 http://grabacr.net/archives/1851 一旦はこの記事で紹介されている方法で対応してみます。
ikadzuchi

2019/08/31 13:47

> 代替のエンコーダーに心当たりはありますか? PNGエンコードのライブラリといえば基本のlibpngくらいしか知りませんが、これでも最高圧縮(最低速)設定では.NETのものより高圧縮なのではないかという気がしています。 そしてOXamarinさんの回答のOptiPngは名前をよく聞きますね。これがC#から使えるならなかなか良さそうに思います。 > これは私の勘違いでした そうでしたか。 > 似たような記事がありました なるほど。残念ながら私の詳しくない分野ですね。
guest

0

そもそも、最終目的の「リッチテキストボックスに、より多くの画像を高速に表示したい。」に必要なことなのでしょうか?
一般的に高圧縮なものから元のデータを得ようとすると、復元するのに時間がかかると思います。
元サイズのデータを読み込む時間と圧縮したデータを読み込む時間の差とデータを復元する時間とどちらが時間を要するかなどあるかもしれませんが、この部分を改善してもそこまで効果がないのではないかと思います。
まずはどこに時間がかかっているのかなるべく細かく時間を計測し、改善すべき点を明確にする必要があるかと思います。

また、こういった問題はどこまで見えない部分への処理をバックグラウンドで行えるかではないでしょうか?
具体的なアルゴリズムに関しては分かりませんが、見える部分の処理が終了次第、UIを操作可能とし、見えない部分はバックグラウンドで順次処理するなど、ユーザーがストレスを感じないように処理するしかないのではないかと思います。

投稿2019/08/31 02:53

YAmaGNZ

総合スコア10251

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

OXamarin

2019/08/31 06:08

ありがとうございます。 >こういった問題はどこまで見えない部分への処理をバックグラウンドで行えるかではないでしょうか? 確かに、これは仰る通りですね。 性質上、どうしても使い続けると重くなっていってしまうのでどうしようか悩んでいましたが見える部分だけを根本的な所から処理すればストレスを無くせると思うのでそのように対応したいと思います。
guest

0

BitmapSourceをそのままバイナリにするのではなく、
圧縮を掛けてからバイナリにしてやればよいと考えました。

GoogleがオススメするOptiPngのロジックを実装している(らしい)PngCompressorを使用しています。

一度実ファイルとして吐き出さないと指定できないのかな…?
これによって、85KBの元データが39KBのバイナリに出来ました。画質も遜色ないレベルです。

C#

1/// <summary> 2/// BitmapSourceをbyte[]型へ変換 3/// </summary> 4/// <param name="bs"></param> 5/// <returns></returns> 6public static byte[] ToBinary(this BitmapSource bs, bool isIcon) 7{ 8 if (bs == null) return null; 9 10 var encoder = new PngBitmapEncoder(); 11 using (var ms = new MemoryStream()) 12 { 13 encoder.Frames.Add(CreateBitmapFrame(bs, isIcon)); 14 encoder.Save(ms); 15 ms.Seek(0, SeekOrigin.Begin); 16 //ms.ToArray()だとメモリに持ってしまうので多少無駄でもGetBufferで返す 17 //.NET Framework 4.6.1以降ならTryGetBufferを使えた 18 return ms.GetBuffer(); 19 } 20} 21 22/// <summary> 23/// 24/// </summary> 25/// <param name="bitmapSource"></param> 26/// <param name="isIcon"></param> 27/// <returns></returns> 28private static BitmapFrame CreateBitmapFrame(BitmapSource bitmapSource, bool isIcon) 29{ 30 //アイコンでなければ画像を圧縮する 31 if (!isIcon) 32 { 33 var inputPngFile = Path.Combine(TempFolderPath, Guid.NewGuid() + ".png"); 34 var outputPngFile = Path.Combine(TempFolderPath, Guid.NewGuid() + ".png"); 35 using (var fs = new FileStream(inputPngFile, FileMode.CreateNew)) 36 { 37 try 38 { 39 //PNGを書き出す 40 var encoder = new PngBitmapEncoder(); 41 encoder.Frames.Add(BitmapFrame.Create(bitmapSource)); 42 encoder.Save(fs); 43 44 //出力したPNGを圧縮する 45 var comp = new PNGCompressor(); 46 comp.CompressImageLossy(inputPngFile, outputPngFile); 47 return BitmapFrame.Create(new Uri(outputPngFile)); 48 } 49 catch 50 { 51 //iconとかの非常に小さいPng(1KBとか)だと圧縮時に画像が壊れてしまっていたので 52 //引数のbitmapSourceを返す 53 } 54 } 55 } 56 57 return BitmapFrame.Create(bitmapSource); 58}

投稿2019/08/31 07:42

編集2019/08/31 07:54
OXamarin

総合スコア59

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

現状で不具合ないならそんでいいんでは。

投稿2019/08/31 00:02

y_waiwai

総合スコア87774

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問