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

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

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

Microsoft Visual Basic .NETのことで、Microsoft Visual Basic(VB6)の後継。 .NET環境向けのプログラムを開発することができます。 現在のVB.NETでは、.NET Frameworkを利用して開発を行うことが可能です。

Q&A

解決済

4回答

4552閲覧

VB.netでListViewに約4100枚のjpg画像ファイルを読み込んでサムネイルを表示しているが約6分かかるのでもっと早くしたい

tada_tadaa

総合スコア111

VB.NET

Microsoft Visual Basic .NETのことで、Microsoft Visual Basic(VB6)の後継。 .NET環境向けのプログラムを開発することができます。 現在のVB.NETでは、.NET Frameworkを利用して開発を行うことが可能です。

0グッド

2クリップ

投稿2021/12/30 14:17

編集2021/12/31 01:34

VB.netでListViewに約4100枚のjpg画像ファイルを読み込んでサムネイルで表示しているのですが、ボタンを押してサムネイルが表示されるまで約6分間かかります。これをもっと早く読み込めるように改良したいのですがどうすればいいのかわかりません。画質は大幅に下がっても構いません。

使用しているパソコンのスペック
使用OS Windows10
メモリ 8GB
CPU   Intel Core i5-3230M 2.60GHz
処理の実行中は実行しているプロセスのCPU使用率が35パーセントぐらいになります。

ここを参考にしました

VB

1Public Class Form1 2 3 ' 幅w、高さhのImageオブジェクトを作成 4 Function createThumbnail(ByVal image As Image, ByVal w As Integer, ByVal h As Integer) As Image 5 Dim canvas As New Bitmap(w, h) 6 7 Dim g As Graphics = Graphics.FromImage(canvas) 8 g.FillRectangle(New SolidBrush(Color.White), 0, 0, w, h) 9 10 Dim fw As Double = CDbl(w) / CDbl(image.Width) 11 Dim fh As Double = CDbl(h) / CDbl(image.Height) 12 Dim scale As Double = Math.Min(fw, fh) 13 14 Dim w2 As Integer = CInt(image.Width * scale) 15 Dim h2 As Integer = CInt(image.Height * scale) 16 17 g.DrawImage(image, (w - w2) \ 2, (h - h2) \ 2, w2, h2) 18 g.Dispose() 19 20 Return canvas 21 End Function 22 23 ' ButtonのClickイベントのハンドラ 24 Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click 25 26 Dim imageDir As String = "C:\Users\user\Pictures" ' 画像ディレクトリ 27 Dim jpgFiles As String() = System.IO.Directory.GetFiles(imageDir, "*.jpg") 28 29 Dim width As Integer = 100 30 Dim height As Integer = 80 31 32 ImageList1.ImageSize = New Size(width, height) 33 ListView1.LargeImageList = ImageList1 34 35  Dim original As Image 36 For i As Integer = 0 To jpgFiles.Length - 1 37 38 Try 39 original = Bitmap.FromFile(jpgFiles(i)) 40 Catch 41 original = My.Resources.Resource1._error 42 End Try 43 44 Dim thumbnail As Image = createThumbnail(original, width, height) 45 46 ImageList1.Images.Add(thumbnail) 47 ListView1.Items.Add(jpgFiles(i), i) 48 49 original.Dispose() 50 thumbnail.Dispose() 51 Next 52 End Sub 53 54End Class

お手数をおかけしますがよろしくお願いいたします。

追記
コードの修正によって少し早くなったのでそれを追記します。
以下のコードでほぼ2分、メモリ使用量は250メガバイトほどに短縮できました。

VB

1Public Class Form1 2 3 ' 幅w、高さhのImageオブジェクトを作成 4 Function createThumbnail(ByVal image As Image, ByVal w As Integer, ByVal h As Integer) As Image 5 Dim canvas As New Bitmap(w, h) 6 7 Dim g As Graphics = Graphics.FromImage(canvas) 8 g.FillRectangle(New SolidBrush(Color.White), 0, 0, w, h) 9 10 Dim fw As Double = CDbl(w) / CDbl(image.Width) 11 Dim fh As Double = CDbl(h) / CDbl(image.Height) 12 Dim scale As Double = Math.Min(fw, fh) 13 14 Dim w2 As Integer = CInt(image.Width * scale) 15 Dim h2 As Integer = CInt(image.Height * scale) 16 17 g.DrawImage(image, (w - w2) \ 2, (h - h2) \ 2, w2, h2) 18 g.Dispose() 19 20 Return canvas 21 End Function 22 23 ' ButtonのClickイベントのハンドラ 24 Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click 25 Dim cnt As Integer = 0 26 27 Dim imageDir As String = "C:\Users\user\Pictures" ' 画像ディレクトリ 28 Dim jpgFiles As String() = System.IO.Directory.GetFiles(imageDir, "*.jpg") 29 30 Dim width As Integer = 100 31 Dim height As Integer = 80 32 33 ImageList1.ImageSize = New Size(width, height) 34 ListView1.LargeImageList = ImageList1 35 36 Dim thumbnail(jpgFiles.Length - 1) As Image 37 38 Dim original As Image 39 For i As Integer = 0 To jpgFiles.Length - 1 '①のブレイクポイントを置く 40 Try 41 original = Bitmap.FromFile(jpgFiles(i)) 42 Catch 43 original = My.Resources.Resource1._error 44 End Try 45 thumbnail(i) = createThumbnail(original, width, height) 46 original.Dispose() 47 Next 48 49 ImageList1.Images.AddRange(thumbnail) '②のブレイクポイントを置く 50 51 For i As Integer = 0 To jpgFiles.Length - 1 52 ListView1.Items.Add(jpgFiles(i), i) 53 Next 54 End Sub 55 56End Class

上記のコードでブレイクポイントを
①For i As Integer = 0 To jpgFiles.Length - 1
の部分と
②ImageList1.Images.AddRange(thumbnail)
の部分に置いてみて時間を計測したところ、Button1を押してから①の部分までで約1秒かかりました。
次に①から②の所までは1分38秒かかりました。次に②から最後まで走らせたら22秒かかりました。
全体で約2分かかってますが、①から②までのサムネイルを作成する処理に時間がかかっているようです。
作ったサムネイルをImageList1に追加するのとListView1.Items.Addの部分で22秒なので、サムネイルを作成する部分をもっと改善出来れば早くなると思います。

それと作っている物ですが
「Visual Studio 2017」でプロジェクトの作成は「Visual Basic」の「Windowsフォームアプリケーション(.Net Framework)」を選択してプロジェクトを作成しています。どのような目的の物を作るかというと、jpg画像ファイルビューワーです。まずディレクトリを選択してそこにある画像ファイルの一覧をサムネイルで表示して、サムネイルのどれかをダブルクリックすると別ウィンドウでその画像が拡大表示されるといったものです。

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

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

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

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

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

vann_2921

2021/12/30 15:05

thumbnailを配列に保存しておいてForを抜けてからImageList1.Images.AddRangeで追加したら何か変わりますかね?
vann_2921

2021/12/30 15:35

手元の環境で試してみたのですが、ListViewの描画の遅さが原因のようでこれを解決するのは難しいかと思われます。軽くする方法としてListViewには仮想モードというのがあるようなので調べてみてください。 ちなみにそのサムネイルは絶対に4100枚同時に表示しないといけないものでしょうか?
tada_tadaa

2021/12/30 15:59

コメントありがとうございます。 >thumbnailを配列に保存しておいてForを抜けてからImageList1.Images.AddRangeで追加したら何か変わりますかね? 以下のように変更してみて試してみましたが、実行プロセスがメモリを4Gバイト以上消費して、途中でエラーで(おそらくメモリオーバーだと思います)止まってしまいました。 ' ButtonのClickイベントのハンドラ Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Dim cnt As Integer = 0 Dim imageDir As String = "C:\Users\user\Pictures" ' 画像ディレクトリ Dim jpgFiles As String() = System.IO.Directory.GetFiles(imageDir, "*.jpg") Dim width As Integer = 100 Dim height As Integer = 80 ImageList1.ImageSize = New Size(width, height) ListView1.LargeImageList = ImageList1 Dim thumbnail(jpgFiles.Length) As Image Dim original As Image For i As Integer = 0 To jpgFiles.Length - 1 Try original = Bitmap.FromFile(jpgFiles(i)) Catch original = My.Resources.Resource1._error End Try thumbnail(i) = createThumbnail(original, width, height) Next For i As Integer = 0 To jpgFiles.Length - 1 ListView1.Items.Add(jpgFiles(i), i) Next ImageList1.Images.AddRange(thumbnail) End Sub >ListViewには仮想モードというのがあるようなので調べてみてください。 調べてみようと思います。ただ今日は遅いので明日調べる事になると思います。 >ちなみにそのサムネイルは絶対に4100枚同時に表示しないといけないものでしょうか? 絶対にというわけではないです。例えば、先頭の画像ファイル千枚を読み込んだ時点でListViewにサムネイルで表示して、バックグラウンドでは残りの三千枚を処理して、順番に処理が終わった画像ファイルからListViewに追加していくというふうに、同時にではなく、時間をずらしても大丈夫です。
tada_tadaa

2021/12/30 16:43

以下のコードでほぼ2分、メモリ使用量は250メガバイトほどに短縮できました。 ' ButtonのClickイベントのハンドラ Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Dim cnt As Integer = 0 Dim imageDir As String = "C:\Users\user\Pictures" ' 画像ディレクトリ Dim jpgFiles As String() = System.IO.Directory.GetFiles(imageDir, "*.jpg") Dim width As Integer = 100 Dim height As Integer = 80 ImageList1.ImageSize = New Size(width, height) ListView1.LargeImageList = ImageList1 Dim thumbnail(jpgFiles.Length - 1) As Image Dim original As Image For i As Integer = 0 To jpgFiles.Length - 1 Try original = Bitmap.FromFile(jpgFiles(i)) Catch original = My.Resources.Resource1._error End Try thumbnail(i) = createThumbnail(original, width, height) original.Dispose() Next ImageList1.Images.AddRange(thumbnail) For i As Integer = 0 To jpgFiles.Length - 1 ListView1.Items.Add(jpgFiles(i), i) Next End Sub KOZ6.0様、コメントありがとうございます。仮想化について調べてみます。ただ情報量が少ないようで、いろいろと試してみないといけないようで今日は遅いので明日になるかと思います。
退会済みユーザー

退会済みユーザー

2021/12/30 23:24

ボトルネックがどこにあるかの調べはついているのでしょうか? サムネイルの作成か、ListView への表示か、どっちでしょう? 何を作っているか (WinForms? ASP.NET Web Forms? その他?) を書いてください。
tada_tadaa

2021/12/31 00:29

SurferOnWww様 >ボトルネックがどこにあるかの調べはついているのでしょうか? 僕の2021/12/31 01:43 の上記のコードでブレイクポイントを ①For i As Integer = 0 To jpgFiles.Length - 1 の部分と ②ImageList1.Images.AddRange(thumbnail) の部分に置いてみて時間を計測したところ、Button1を押してから①の部分までで約1秒かかりました。 次に①から②の所までは1分38秒かかりました。次に②から最後まで走らせたら22秒かかりました。 全体で約2分かかってますが、①から②までのサムネイルを作成する処理に時間がかかっているようです。 作ったサムネイルをImageList1に追加するのとListView1.Items.Addの部分で22秒なので、サムネイルを作成する部分をもっと改善出来れば早くなると思います。
tada_tadaa

2021/12/31 00:37

>何を作っているか (WinForms? ASP.NET Web Forms? その他?) を書いてください。 「Visual Studio 2017」でプロジェクトの作成は「Visual Basic」の「Windowsフォームアプリケーション(.Net Framework)」を選択してプロジェクトを作成しています。どのような目的の物を作るかというと、jpg画像ファイルビューワーです。まずディレクトリを選択してそこにある画像ファイルの一覧をサムネイルで表示して、サムネイルのどれかをダブルクリックすると別ウィンドウでその画像が拡大表示されるといったものです。
退会済みユーザー

退会済みユーザー

2021/12/31 01:05

上記(ボトルネックと何を作っているか)は質問欄を編集して追加情報として追記願います。また「僕の2021/12/31 01:43 の上記のコード」も質問欄を編集して追記願います(元のコードはそのままにして、追記という形でお願いします)。ここコメント欄は「質問への追記・修正の依頼」の場所ですので。 サムネイルの作成にタスク並列ライブラリ (TPL) などの使用は検討されたでしょうか?
退会済みユーザー

退会済みユーザー

2021/12/31 01:58 編集

このコメントは回答欄に移しました。
guest

回答4

0

ベストアンサー

ListViewへのアイテム追加を現状のサムネイル作成→ファイル名とImageListのインデックスではなく
ファイル名のみとしてListViewに追加。
サムネイルは別スレッドでキャッシュを作成しつつ、ListViewへの描画はOwnerDrawにてアイテムを描画する時にキャッシュにあればキャッシュから、キャッシュにない場合はその場でサムネイル作成→キャッシュへ追加→描画
とすれば高速化できないでしょうか。
ちょっと古い記事ですがサムネイル画像の生成と表示が参考になるのではないかと思います。

とりあえず動くものを作ってみました。
言いたいことは伝わるのではないかと思います。

VBNET

1Imports System.Threading 2 3Public Class Form1 4 5 Const thumbnailwidth As Integer = 100 6 Const thumbnailheight As Integer = 80 7 8 Private listitem As New List(Of ListViewItem) 9 Private thumbnailCache As New Dictionary(Of String, Image) 10 Private listlock As New Object 11 Private cts As CancellationTokenSource 12 Private cachetask As Task 13 14 Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load 15 Dim imagelist1 As New ImageList 16 imagelist1.ImageSize = New Size(thumbnailwidth, thumbnailheight) 17 ListView1.LargeImageList = imagelist1 18 ListView1.OwnerDraw = True 19 End Sub 20 21 22 Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click 23 Dim imageDir As String = "C:\Users\user\Pictures" ' 画像ディレクトリ 24 Dim jpgFiles As IEnumerable = System.IO.Directory.EnumerateFiles(imageDir, "*.jpg", IO.SearchOption.AllDirectories) 25 26 'サムネイルキャッシュ作成中であればキャンセル 27 If cachetask?.Status = TaskStatus.Running Then 28 cts.Cancel() 29 cachetask.Wait() 30 End If 31 32 listitem.Clear() 33 thumbnailCache.Clear() 34 35 For Each file As String In jpgFiles 36 Dim item = New ListViewItem(file) 37 listitem.Add(item) 38 Next 39 ListView1.VirtualMode = True 40 ListView1.VirtualListSize = listitem.Count 41 42 'キャッシュ作成開始 43 CreateCache() 44 End Sub 45 46 Private Sub ListView1_RetrieveVirtualItem(sender As Object, e As RetrieveVirtualItemEventArgs) Handles ListView1.RetrieveVirtualItem 47 If e.Item Is Nothing Then e.Item = listitem(e.ItemIndex) 48 End Sub 49 50 51 Function createThumbnail(filename As String) As Image 52 Dim canvas As New Bitmap(thumbnailwidth, thumbnailheight) 53 54 Using original = Bitmap.FromFile(filename) 55 56 Using g As Graphics = Graphics.FromImage(canvas) 57 Using WhiteBrush = New SolidBrush(Color.White) 58 g.FillRectangle(WhiteBrush, 0, 0, thumbnailwidth, thumbnailheight) 59 End Using 60 61 Dim fw As Double = CDbl(thumbnailwidth) / CDbl(original.Width) 62 Dim fh As Double = CDbl(thumbnailheight) / CDbl(original.Height) 63 Dim scale As Double = Math.Min(fw, fh) 64 65 Dim w2 As Integer = CInt(original.Width * scale) 66 Dim h2 As Integer = CInt(original.Height * scale) 67 68 g.DrawImage(original, (thumbnailwidth - w2) \ 2, (thumbnailheight - h2) \ 2, w2, h2) 69 End Using 70 End Using 71 72 Return canvas 73 End Function 74 75 76 Private Sub CreateCache() 77 cts = New CancellationTokenSource 78 cachetask = Task.Factory.StartNew(Sub() 79 Try 80 81 For Each item In listitem 82 If thumbnailCache.ContainsKey(item.Text) = False Then 83 Dim thumbnail As Image = createThumbnail(item.Text) 84 SyncLock listlock 85 If thumbnailCache.ContainsKey(item.Text) = False Then 86 thumbnailCache.Add(item.Text, thumbnail) 87 End If 88 End SyncLock 89 Threading.Thread.Sleep(0) 90 cts.Token.ThrowIfCancellationRequested() 91 End If 92 Next 93 94 Catch ex As OperationCanceledException 95 Console.WriteLine("キャッシュ作成キャンセル") 96 End Try 97 98 End Sub, cts.Token) 99 100 End Sub 101 102 Private Sub ListView1_DrawItem(sender As Object, e As DrawListViewItemEventArgs) Handles ListView1.DrawItem 103 Dim filiename As String = e.Item.Text 104 Dim thumbnail As Image 105 106 'サムネイルキャッシュに含まれている場合はキャッシュを 107 'そうでない場合はサムネイルを作成、キャッシュに追加したのち描画する 108 If thumbnailCache.ContainsKey(filiename) Then 109 thumbnail = thumbnailCache(filiename) 110 Else 111 thumbnail = createThumbnail(filiename) 112 SyncLock listlock 113 If thumbnailCache.ContainsKey(filiename) = False Then 114 thumbnailCache.Add(filiename, thumbnail) 115 End If 116 End SyncLock 117 End If 118 119 'アイテムの描画 120 Dim imagerect As New Rectangle(New Point(e.Bounds.X + ((e.Bounds.Width - thumbnail.Width) / 2), e.Bounds.Y), New Size(thumbnail.Width, thumbnail.Height)) 121 122 e.DrawDefault = False 123 e.DrawBackground() 124 e.Graphics.DrawImage(thumbnail, imagerect) 125 126 Dim stringFormat = New StringFormat() 127 stringFormat.Alignment = StringAlignment.Center 128 stringFormat.LineAlignment = StringAlignment.Center 129 e.Graphics.DrawString(e.Item.Text, ListView1.Font, Brushes.Black, New RectangleF(e.Bounds.X, e.Bounds.Y + imagerect.Height + 5, e.Bounds.Width, e.Bounds.Height - imagerect.Height - 5), stringFormat) 130 131 e.DrawFocusRectangle() 132 End Sub 133 134End Class

投稿2021/12/30 23:00

編集2021/12/31 02:13
YAmaGNZ

総合スコア10251

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

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

tada_tadaa

2021/12/31 02:39

回答コメントありがとうございます。 YAmaGNZ様のコードを試してみた所、ボタンを押してすぐ(約1秒)にサムネイルが表示されました。劇的に早くなりました。 コードの内容は初心者の僕には難しいですが、これから研究してみようと思います。それにしても、がんばっても2分程度だったのが1秒で表示されるとは、工夫次第でこんなにも改善できるのだと勉強になります。
YAmaGNZ

2022/01/01 03:19

サムネイルの生成自体を高速化するのであれば、ImageMagickなどのライブラリを使用することも考慮に入れたほうがいいかもしれません。 当方で約3900枚のJPEGで試したところ(私の提示したキャッシュ作成を別Taskで行う状態) 従来の処理:約60~70秒 MagickImage:約35秒 といった結果になりました。 SurferOnWwwさんの仰るようにTPLを使用すると i7-11700の環境で約10秒でキャッシュの作成が終了する状態になりました。
guest

0

①から②までのサムネイルを作成する処理に時間がかかっているようです。

タスク並列ライブラリ (TPL) などの使用の具体例を紹介しておきます。

タスク並列ライブラリ (TPL) その 2
http://surferonwww.info/BlogEngine/post/2021/07/20/task-parallel-library-2nd.aspx

上記は TPL に代えて、複数のタスクをスレッドプールで実行するように設定し、並列化については OS 任せにした場合と TPL を使った場合の比較の記事です。

3 秒かかるタスクを 100 個実行するので、同期の場合は 300 秒かかるはずですが、記事の例では Core i7-9700K 8 コア 8 論理プロセッサで約 25 秒(12 倍早い)という結果でした。

コア数が少ないとあまり効果が無いかもしれませんが、質問者さんの i5-3230M もマルチコア・マルチスレッドのようですのでそれなりに早くなると思われます。

投稿2021/12/31 01:41

退会済みユーザー

退会済みユーザー

総合スコア0

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

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

tada_tadaa

2021/12/31 12:56

回答ありがとうございます。 >質問者さんの i5-3230M もマルチコア・マルチスレッドのようですのでそれなりに早くなると思われます。 教えていただいたリンク先のページをみてみたのですが、VB初心者の自分には難易度が高いと思われましたので、比較的難易度の低そうなBackgroundWorkerを使った下記のコードを書いてみました。また、下記のコードは質問欄を編集して追記したかったのですが、文字数の制限にひっかかったので申し分かりませんがこちらのコメント欄に書かせていただくことにしました。 ベストアンサーに選ばせていただいたYAmaGNZ様がボタンを押した瞬間にサムネイルの一覧を表示してくれるコードを書いてくれましたがデメリットもありまして、勢いよくスクロールするとサムネイルの描画が追い付かず、数秒間待たなければならない現象が起こります。恐らくListViewの仮想モードを使っているからだと思いますが(間違ってたらすみません)、一方で仮想モードを使わない方法だとサムネイル表示まで時間がかかりますが、いったんサムネイルが表示されると素早くスクロールしてもすらすらとサムネイルを表示してくれます。ですので、使うユーザーが仮想モードと普通の読み込みを選択できるような設定にする事にしました。それで普通の読み込みでマルチスレッドを使えば多少早くなるのではないかと参考になるかは分かりませんが試しにコードを書いてみました。 ツールボックスからBackgroundWorkerを4つ追加しています。 処理をしている間、実行プロセスのCPU使用率が70から80パーセントになって全体のCPU使用率が100パーセントになります。時間はボタンを押してから1分20秒ほどでサムネイル一覧の表示が終わります。メモリ使用量は180メガバイトぐらいです。 Imports System.Threading Public Class Form1 Const thumbnailwidth As Integer = 100 Const thumbnailheight As Integer = 80 Public imagelist1 As New ImageList Public imageDir As String = "C:\Users\user\Pictures" ' 画像ディレクトリ Public jpgFiles As String() = System.IO.Directory.GetFiles(imageDir, "*.jpg") Public flag1 As Boolean = False Public flag2 As Boolean = False Public flag3 As Boolean = False Public flag4 As Boolean = False Public num1 As Integer = jpgFiles.Length / 4 Public num2 As Integer = jpgFiles.Length / 2 Public num3 As Integer = jpgFiles.Length / 2 + jpgFiles.Length / 4 Public thumbnail(num1) As Image Public thumbnail2(num2 - num1) As Image Public thumbnail3(num3 - num2) As Image Public thumbnail4(jpgFiles.Length - num3) As Image Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load imagelist1.ImageSize = New Size(thumbnailwidth, thumbnailheight) ListView1.LargeImageList = imagelist1 ListView1.OwnerDraw = False End Sub Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click 'スレッドの開始 BackgroundWorker1.RunWorkerAsync() 'スレッドの開始 BackgroundWorker2.RunWorkerAsync() 'スレッドの開始 BackgroundWorker3.RunWorkerAsync() 'スレッドの開始 BackgroundWorker4.RunWorkerAsync() While True If flag1 = True And flag2 = True And flag3 = True And flag4 = True Then For i As Integer = 0 To jpgFiles.Length - 1 ListView1.Items.Add(jpgFiles(i), i) Next Exit While End If System.Threading.Thread.Sleep(400) End While End Sub Function createThumbnail(ByVal image As Image, ByVal w As Integer, ByVal h As Integer) As Image Dim canvas As New Bitmap(w, h) Dim g As Graphics = Graphics.FromImage(canvas) g.FillRectangle(New SolidBrush(Color.White), 0, 0, w, h) Dim fw As Double = CDbl(w) / CDbl(image.Width) Dim fh As Double = CDbl(h) / CDbl(image.Height) Dim scale As Double = Math.Min(fw, fh) Dim w2 As Integer = CInt(image.Width * scale) Dim h2 As Integer = CInt(image.Height * scale) g.DrawImage(image, (w - w2) \ 2, (h - h2) \ 2, w2, h2) g.Dispose() Return canvas End Function Function createThumbnail2(ByVal image As Image, ByVal w As Integer, ByVal h As Integer) As Image Dim canvas As New Bitmap(w, h) Dim g As Graphics = Graphics.FromImage(canvas) g.FillRectangle(New SolidBrush(Color.White), 0, 0, w, h) Dim fw As Double = CDbl(w) / CDbl(image.Width) Dim fh As Double = CDbl(h) / CDbl(image.Height) Dim scale As Double = Math.Min(fw, fh) Dim w2 As Integer = CInt(image.Width * scale) Dim h2 As Integer = CInt(image.Height * scale) g.DrawImage(image, (w - w2) \ 2, (h - h2) \ 2, w2, h2) g.Dispose() Return canvas End Function Function createThumbnail3(ByVal image As Image, ByVal w As Integer, ByVal h As Integer) As Image Dim canvas As New Bitmap(w, h) Dim g As Graphics = Graphics.FromImage(canvas) g.FillRectangle(New SolidBrush(Color.White), 0, 0, w, h) Dim fw As Double = CDbl(w) / CDbl(image.Width) Dim fh As Double = CDbl(h) / CDbl(image.Height) Dim scale As Double = Math.Min(fw, fh) Dim w2 As Integer = CInt(image.Width * scale) Dim h2 As Integer = CInt(image.Height * scale) g.DrawImage(image, (w - w2) \ 2, (h - h2) \ 2, w2, h2) g.Dispose() Return canvas End Function Function createThumbnail4(ByVal image As Image, ByVal w As Integer, ByVal h As Integer) As Image Dim canvas As New Bitmap(w, h) Dim g As Graphics = Graphics.FromImage(canvas) g.FillRectangle(New SolidBrush(Color.White), 0, 0, w, h) Dim fw As Double = CDbl(w) / CDbl(image.Width) Dim fh As Double = CDbl(h) / CDbl(image.Height) Dim scale As Double = Math.Min(fw, fh) Dim w2 As Integer = CInt(image.Width * scale) Dim h2 As Integer = CInt(image.Height * scale) g.DrawImage(image, (w - w2) \ 2, (h - h2) \ 2, w2, h2) g.Dispose() Return canvas End Function Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork Dim original As Image For i As Integer = 0 To num1 - 1 Try original = Bitmap.FromFile(jpgFiles(i)) Catch original = My.Resources.Resource1._error End Try thumbnail(i) = createThumbnail(original, thumbnailwidth, thumbnailheight) original.Dispose() Next For i As Integer = 0 To num1 - 1 imagelist1.Images.Add(thumbnail(i)) thumbnail(i).Dispose() Next flag1 = True End Sub Private Sub BackgroundWorker2_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker2.DoWork For i As Integer = num1 To num2 - 1 Dim original As Image Try original = Bitmap.FromFile(jpgFiles(i)) Catch original = My.Resources.Resource1._error End Try thumbnail2(i - num1) = createThumbnail2(original, thumbnailwidth, thumbnailheight) original.Dispose() Next For i As Integer = num1 To num2 - 1 imagelist1.Images.Add(thumbnail2(i - num1)) thumbnail2(i - num1).Dispose() Next flag2 = True End Sub Private Sub BackgroundWorker3_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker3.DoWork Dim original As Image For i As Integer = num2 To num3 - 1 Try original = Bitmap.FromFile(jpgFiles(i)) Catch original = My.Resources.Resource1._error End Try thumbnail3(i - num2) = createThumbnail3(original, thumbnailwidth, thumbnailheight) original.Dispose() Next For i As Integer = num2 To num3 - 1 imagelist1.Images.Add(thumbnail3(i - num2)) thumbnail3(i - num2).Dispose() Next flag3 = True End Sub Private Sub BackgroundWorker4_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker4.DoWork Dim original As Image For i As Integer = num3 To jpgFiles.Length - 1 Try original = Bitmap.FromFile(jpgFiles(i)) Catch original = My.Resources.Resource1._error End Try thumbnail4(i - num3) = createThumbnail4(original, thumbnailwidth, thumbnailheight) original.Dispose() Next For i As Integer = num3 To jpgFiles.Length - 1 imagelist1.Images.Add(thumbnail4(i - num3)) thumbnail4(i - num3).Dispose() Next flag4 = True End Sub End Class
退会済みユーザー

退会済みユーザー

2022/01/01 00:12 編集

6 分かかっていたものが 1 分 20 秒に短縮されたと言うことですと、2 コア 4 スレッドの i5-3230M ですと期待以上で、TPL を使ってもそれ以上の改善は望めないような気がします。(気がするだけで確証などはもちろんないですが) もし、それでは満足されてないのであれば、サムネイル作成の高速化の部分のみ新たに別のスレッドをたてて質問されてはいかがですか? または、あらかじめサムネイル画像を作っておくなど違うアプローチも考えてみるとか。
guest

0

ListViewには仮想モードいう表示範囲しか描画しないようにして処理の軽減が可能なそうです。
検索のキーワードとしては「.net listview 描画 遅い」で検索するとKOZ6.0さんが質問への追記・修正の依頼にて提示された記事が出てきます。
試しに使ってみたコードです。参考にしたサイト
効果としては120*120のビットマップをListViewに10000件追加する処理で、VirtualModeを使わない場合は3分ちょっとかかっていたのが5秒程度になりました。

VB

1Public Class Form1 2 Public Sub New() 3 4 ' この呼び出しはデザイナーで必要です。 5 InitializeComponent() 6 Dim Images As New List(Of Bitmap) 7 ' InitializeComponent() 呼び出しの後で初期化を追加します。 8 For i As Integer = 0 To 9999 9 Dim thumbnail As New Bitmap(120, 120) 10 Using Graphics1 As Graphics = Graphics.FromImage(thumbnail) 11 Graphics1.FillRectangle(New SolidBrush(Color.FromArgb(i / 1000 * 25, 0, 0)), New Rectangle(0, 0, 120, 120)) 12 End Using 13 Images.Add(thumbnail) 14 Next 15 ImageList1.Images.AddRange(Images.ToArray()) 16 ListView1.VirtualListSize = 10000 '10000は表示する画像の数に合わせて下さい。 17 'ListView1.VirtualMode = True 'この例ではデザイナのプロパティで変更してます。 18 End Sub 19 20 Private Sub ListView1_RetrieveVirtualItem(sender As Object, e As RetrieveVirtualItemEventArgs) Handles ListView1.RetrieveVirtualItem 21 If e.Item Is Nothing Then e.Item = New ListViewItem(e.ItemIndex.ToString(), e.ItemIndex) 22 End Sub 23End Class

注意事項としてはVirtualModeがTrueのときはListViewのItemsプロパティでのアイテムの追加ができなくなります。
例ではRetrieveVirtualItemイベントのRetrieveVirtualItemEventArgsを介して、アイテムが未設定であれば新しくアイテムを設定しています。

追記

比較用のVirtualModeを使わないコード

VB

1Public Class Form1 2 Private Images As New List(Of Bitmap) 3 Private ListViewItems As New List(Of ListViewItem) 4 Public Sub New() 5 6 ' この呼び出しはデザイナーで必要です。 7 InitializeComponent() 8 9 ' InitializeComponent() 呼び出しの後で初期化を追加します。 10 For i As Integer = 0 To 9999 11 Dim thumbnail As New Bitmap(120, 120) 12 Using Graphics1 As Graphics = Graphics.FromImage(thumbnail) 13 Graphics1.FillRectangle(New SolidBrush(Color.FromArgb(i / 1000 * 25, 0, 0)), New Rectangle(0, 0, 120, 120)) 14 End Using 15 Images.Add(thumbnail) 16 ListViewItems.Add(New ListViewItem(i.ToString(), i)) 17 Next 18 ImageList1.Images.AddRange(Images.ToArray()) 19 ListView1.Items.AddRange(ListViewItems.ToArray()) 20 End Sub 21End Class

追記2

画像の生成が遅いときは必要になってから画像を生成する方法にすると立ち上がりが早くなるかもしれません。サンプルでは早くなりましたが元の画像が重いと効果があるかは分かりません。

VB

1Dim i As Integer = 0 '生成した画像の数 2 Private Sub ListView1_RetrieveVirtualItem(sender As Object, e As RetrieveVirtualItemEventArgs) Handles ListView1.RetrieveVirtualItem 3 'パスから画像を読み込む場合は事前にファイルのパスのリストを保存しておく 4 If e.ItemIndex < 10000 AndAlso e.Item Is Nothing Then 5 'i番目の画像を生成する 6 Dim thumbnail As New Bitmap(120, 120) 7 Using Graphics1 As Graphics = Graphics.FromImage(thumbnail) 8 Graphics1.FillRectangle(New SolidBrush(Color.FromArgb(i / 1000 * 25, 0, 0)), New Rectangle(0, 0, 120, 120)) 9 End Using 10 ImageList1.Images.Add(thumbnail) 11 e.Item = New ListViewItem(e.ItemIndex.ToString(), e.ItemIndex) 12 i += 1 13 End If 14 End Sub

投稿2021/12/30 17:03

編集2021/12/31 02:41
vann_2921

総合スコア190

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

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

tada_tadaa

2021/12/31 02:04

回答ありがとうございます。 合っているか分かりませんが仮想化してみたつもりのコードです。ListView1のプロパティVirtualModeはTrueに設定してあります。 全体で1分48秒ほどかかりました。 ImageList1.Images.AddRange(thumbnail) が書いてある行にブレイクポイントを設置したらそこでストップするまで1分37秒かかりました。 やはりそれより前のサムネイルの作成する処理で時間がかかっていると思われます。 Public Class Form1 ' 幅w、高さhのImageオブジェクトを作成 Function createThumbnail(ByVal image As Image, ByVal w As Integer, ByVal h As Integer) As Image Dim canvas As New Bitmap(w, h) Dim g As Graphics = Graphics.FromImage(canvas) g.FillRectangle(New SolidBrush(Color.White), 0, 0, w, h) Dim fw As Double = CDbl(w) / CDbl(image.Width) Dim fh As Double = CDbl(h) / CDbl(image.Height) Dim scale As Double = Math.Min(fw, fh) Dim w2 As Integer = CInt(image.Width * scale) Dim h2 As Integer = CInt(image.Height * scale) g.DrawImage(image, (w - w2) \ 2, (h - h2) \ 2, w2, h2) g.Dispose() Return canvas End Function ' ButtonのClickイベントのハンドラ Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Dim cnt As Integer = 0 Dim imageDir As String = "C:\Users\user\Pictures" ' 画像ディレクトリ Dim jpgFiles As String() = System.IO.Directory.GetFiles(imageDir, "*.jpg") Dim width As Integer = 100 Dim height As Integer = 80 ImageList1.ImageSize = New Size(width, height) ListView1.LargeImageList = ImageList1 Dim thumbnail(jpgFiles.Length - 1) As Image Dim original As Image For i As Integer = 0 To jpgFiles.Length - 1 Try original = Bitmap.FromFile(jpgFiles(i)) Catch original = My.Resources.Resource1._error End Try thumbnail(i) = createThumbnail(original, width, height) original.Dispose() Next ImageList1.Images.AddRange(thumbnail) 'ここにブレイクポイントを設置、ここまでで1分37秒 ListView1.VirtualListSize = jpgFiles.Length End Sub Private Sub ListView1_RetrieveVirtualItem(sender As Object, e As RetrieveVirtualItemEventArgs) Handles ListView1.RetrieveVirtualItem If e.Item Is Nothing Then e.Item = New ListViewItem(e.ItemIndex.ToString(), e.ItemIndex) End Sub Public Sub New() InitializeComponent() End Sub End Class
YAmaGNZ

2022/01/01 03:19 編集

すみません。コメント場所を間違えました
tada_tadaa

2022/01/08 16:34

vann_2921様に2021/12/31 11:04 にコメントで僕が投稿したプログラムは仮想モードとは言えないようです。改めてvann_2921様の解答などを参考に仮想モードのプログラムを作ってみました。正しく合っているかは分かりませんが、一応仮想モードにはなっているようです。すぐにサムネイルが表示されますし、スクロールするとその都度、サムネイルを描画しているようです。他の誰かの参考になるか分かりませんが以下にプログラムを示します。 Public Class Form1 Public imageDir As String = "C:\Users\user\Pictures" ' 画像ディレクトリ Public jpgFiles As String() = System.IO.Directory.GetFiles(imageDir, "*.jpg", IO.SearchOption.TopDirectoryOnly) Public thumbnail(jpgFiles.Length) As Image Public width1 As Integer = 100 Public height1 As Integer = 80 Public Imagelist1 As New ImageList ' 幅w、高さhのImageオブジェクトを作成 Function createThumbnail(ByVal image As Image, ByVal w As Integer, ByVal h As Integer) As Image Dim canvas As New Bitmap(w, h) Dim g As Graphics = Graphics.FromImage(canvas) g.FillRectangle(New SolidBrush(Color.White), 0, 0, w, h) Dim fw As Double = CDbl(w) / CDbl(image.Width) Dim fh As Double = CDbl(h) / CDbl(image.Height) Dim scale As Double = Math.Min(fw, fh) Dim w2 As Integer = CInt(image.Width * scale) Dim h2 As Integer = CInt(image.Height * scale) g.DrawImage(image, (w - w2) \ 2, (h - h2) \ 2, w2, h2) g.Dispose() Return canvas End Function Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load ListView1.Enabled = True ListView1.Visible = True ListView1.VirtualMode = True ListView1.OwnerDraw = True Imagelist1.ImageSize = New Size(width1, height1) ListView1.LargeImageList = Imagelist1 ListView1.VirtualListSize = jpgFiles.Length For i As Integer = 0 To jpgFiles.Count - 1 thumbnail(i) = Nothing Next End Sub Private Sub ListView1_RetrieveVirtualItem(sender As Object, e As RetrieveVirtualItemEventArgs) Handles ListView1.RetrieveVirtualItem e.Item = New ListViewItem(jpgFiles(e.ItemIndex), e.ItemIndex) End Sub Private Sub ListView1_DrawItem(sender As Object, e As DrawListViewItemEventArgs) Handles ListView1.DrawItem e.DrawDefault = False e.DrawBackground() If thumbnail(e.ItemIndex) Is Nothing Then Dim original As Image Try original = Bitmap.FromFile(jpgFiles(e.ItemIndex)) Catch original = My.Resources.Resource1._error End Try thumbnail(e.ItemIndex) = createThumbnail(original, Width, Height) End If Dim imagerect As New Rectangle(New Point(e.Bounds.X + ((e.Bounds.Width - width1) / 2), e.Bounds.Y), New Size(width1, height1)) e.Graphics.DrawImage(thumbnail(e.ItemIndex), imagerect) Dim stringFormat = New StringFormat() stringFormat.Alignment = StringAlignment.Center stringFormat.LineAlignment = StringAlignment.Center e.Graphics.DrawString(e.Item.Text, ListView1.Font, Brushes.Black, New RectangleF(e.Bounds.X, e.Bounds.Y + imagerect.Height + 5, e.Bounds.Width, e.Bounds.Height - imagerect.Height - 5), stringFormat) e.DrawFocusRectangle() End Sub End Class いまいち仮想モードの仕組みが分からなかったのですが簡単に説明すると、 https://kotatuinu.cocolog-nifty.com/blog/2010/01/listviewcheckbo.html によると 「仮想リストビューとはどういったモノかというと、ListViewは表示だけ行い、表示する項目の管理はプログラムで行うというもの。 VirtualListSizeに表示項目数を指定して、表示する項目はRetrieveVirtualItemイベントで渡す。このイベントで表示する項目のIndexを渡されるので、それに対してListViewItemオブジェクトを渡せばいい。」 だそうです。僕なりの解釈だとListViewに描画の必要性が発生するとRetrieveVirtualItemなどのイベントが発生して、ListView1_RetrieveVirtualItem関数の引数である e As RetrieveVirtualItemEventArgs のeに表示したい文字やインデックス番号を設定するようです。(違うかもしれませんが) 僕の場合は画像も表示するのでListView1_DrawItem関数も呼び出されるようです。 ListView1_DrawItem関数が呼び出されたら、 e.ItemIndexにはListViewが描画したいインデックス番号が格納されているようなので、 If thumbnail(e.ItemIndex) Is Nothing Then の部分で変数thumbnailのインデックス番号e.ItemIndexがNothingかどうかで、そのサムネイルが作成されているかどうか調べて作成されてなければ thumbnail(e.ItemIndex) = createThumbnail(original, Width, Height) の部分でサムネイル画像を作成してthumbnail(e.ItemIndex)に代入しています。 つまりListViewが描画にサムネイル画像が必要な時に、その部分だけのサムネイルを作成して作成したサムネイル画像は変数thumbnailに保持しているので、以後必要になったらすぐに使えるというわけです。 ネットで仮想モードの説明が少ないのと難しい説明が多かったので、少しここで簡単に説明してみました。間違ってたらすみません。
vann_2921

2022/01/08 18:16

フィードバックありがとうございます。 私も仮想モードは初めて使ったので正直何が正解かはよく分かりませんが個人的な見解を述べます。 2021/12/31 11:04のコメントの時点での全体で1分48秒という結果は仮想モードがうまくいっているからだと思います。 なぜなら、立ち上がりが遅いのは仮想モードかどうかよりも画像のロードに時間がかかっているからです。 仮想モード無し : ロード時間 (1分37秒)+ ListViewの描画時間 = 6分 仮想モード有り : ロード時間 (1分37秒)+ ListViewの描画時間 = 1分48秒 そして上記のコメントを見た限りロード時間も改善されたようですが、それはロードを最初に一括で行わずに必要になってからロードするようにしたからだと思います。そうすることで立ち上がりの1分37秒を待たずに動かせるようになったのだと思います。 なので後半の説明は仮想モードかどうかとは関係ないんじゃないかと思います。 あと、個人的な問題ですがコメント欄にコードの全文を乗せるのはやめてほしいです。インデントも無しにコードを張られても読み辛くて何が大事なところかが分からないので。
tada_tadaa

2022/01/09 15:13

僕も2021/12/31 11:04のコメントのプログラムで仮想モードがうまくいっているのかよく分かりません。ただ2022/01/09 01:34 のコメントのプログラムで僕が求めていた1,2秒以内にサムネイルが表示されるものが出来たので、他の誰かの参考になればと思いコードを載せました。 >あと、個人的な問題ですがコメント欄にコードの全文を乗せるのはやめてほしいです。インデントも無しにコードを張られても読み辛くて何が大事なところかが分からないので。 申し訳ありません。ただ、質問欄に追加で記入しようとすると字数制限で書き込めないので仕方なくコメント欄にプログラムを載せています。ネットで検索すると日本語での仮想モードの説明は少ないし、サムネイルを作成するプログラムも見当たらないので結構、貴重な情報だと思うので共有しないともったいないかと思いました。
vann_2921

2022/01/09 19:34

貴重な情報であることは確かなので、なおさら他者に伝えるためには簡潔に要点だけまとめたサンプルコードが書けたらいいと思います。字数制限があるのはそういうことだと思います。 また、どうしても長くなっていしまう場合はQiitaの記事を書くのも有効だと思います。
tada_tadaa

2022/01/10 03:33

時間に余裕があるときにブログ等に載せてみようかと思います。
guest

0

4100枚のjpg画像ファイルを全部リサイズして小さくすればいいです。

投稿2021/12/30 21:53

qqfsdfsafd

総合スコア599

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

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

tada_tadaa

2021/12/31 00:45

ハードディスク内に置いてある画像ファイルを小さい画像に変換するつもりはありません。サムネイルのどれかの画像をダブルクリックすると元の大きな画像を別ウィンドウで開く予定なので、画像ファイルを小さくしたら意味がなくなってしまいます。 そうではなく、プログラムによって処理の内部で小さくリサイズするならありだと思いますが、それなら質問に示したコード内でやっているようですが、時間がもっと早くならないかと模索している所です。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問