ショートカットファイルのJumboアイコン

タグの編集
投稿者 mayopee  (社会人) 投稿日時 2020/6/11 15:49:38
環境:VB2019,.Net4.7.2,WinForm

ショートカットファイル(.lik)の48x48や256x256の大きいサイズのアイコンを取得する方法を教えてください。
System.DrawingのIcon.ExtractAssociatedIconメソッドで取得できるでのですが、既定値の32x32でしか取得できません。
これを、拡大するという手法は最終手段としたいので、他の方法を教えてほしいです。

又、https://stackoverflow.com/questions/28525925/get-icon-128128-file-type-c-sharpを参考にして
IImageList::GetIconも試しましたが、こちらではJumboアイコンは取得できますが、
ショートカットを示す矢印が表示されません。

 Dim img As Bitmap = Icon.ExtractAssociatedIcon("C:\test.lnk").ToBitmap
 PictureBox1.Image = img
投稿者 るきお  (社会人) 投稿日時 2020/6/11 18:23:37
ここのIronRazerzさんが投稿しているプログラムでexeやdllから大きいサイズのアイコンを取得できるようです。

https://social.msdn.microsoft.com/Forums/windows/en-US/a187f98b-9c16-455b-8e69-642f76447b7d/how-to-extract-large-icon-more-than-32-x-32-from-exe-and-dll?forum=vbgeneral

このサンプルはexeやdllなどが対象ですので、lnkは直接は対象にできないかもしれません。
その場合は、lnkのリンク先を取得してから、その上でこのサンプルを適用する形になると思います。

リンク先が xlsx や txt など単なるデータファイルの場合は、関連付けられたアプリケーションを取得した上で、このサンプルを適用する形になると思います。
投稿者 mayopee  (社会人) 投稿日時 2020/6/11 23:36:40
るきお様、いつもありがとうございます。

リンク先を拝見しましたが、自分の求めるものとは少し違うようです。
リンク先のは希望するサイズのアイコンが.EXEファイル等に含まれない場合、拡大して返却するもので
エクスプローラで表示されるアイコンとは異なります。

ショートカットのアイコンはオーバーレイアイコンになっていて、矢印の画像が左下にオーバーレイされています。矢印のオーバーレイがない、Jumboアイコン自体は最初に書いた通りIImageList::GetIconやMicrosoft.WindowsAPICodePackを使って取得できています。

わからないのは、ショートカットを表す矢印の画像の取得と、それをオーバーレイする方法です。
エクスプローラで表示されるアイコンと同じものが取得したいのです。

自分の説明がヘタクソで申し訳ありません。
投稿者 mayopee  (社会人) 投稿日時 2020/6/12 07:38:47
一応、出来ました。るきお様に教えてもらったリンク先が参考になりました。

%windir%\System32\shell32.dllの中にショートカットの矢印アイコンが入っているので
そこからリンク先を参考にして矢印アイコンを抽出して、あらかじめ取得してあるJumboアイコンに
DrawImage/DrawIconすることで出来ました。
フォルダ内に大量のショートカットファイルがあり、それを列挙表示する場合には、速度的に無理があるかもしれません。

エクスプローラではスムーズに表示されるので、もっと別の方法があると思うのですが..........
まだ、もやもや感が残るので、解決マークは付けないでおきます。
投稿者 (削除されました)  () 投稿日時 2020/6/12 11:57:10
(削除されました)
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2020/6/12 14:09:25
> %windir%\System32\shell32.dllの中にショートカットの矢印アイコンが入っているので
#16769 に、7 サイズ (16x16~64x64) の 32bit アイコン含まれていますね。見落としていました。
#30 にある 2 サイズ (32x32, 16x16) の 4bit アイコンの方は発見していたのですが。


> 最初に書いた通りIImageList::GetIconやMicrosoft.WindowsAPICodePackを使って取得できています。
最初の投稿に Microsoft.WindowsAPICodePack の話は出てきてましたっけ。

自分も Microsoft.WindowsAPICodePack で試してはいたのですが、
なかなか期待する結果が得られていません…。


StockIcon から辿れば、linkOverlay が可能なようですが、Jumbo アイコンが使えない様子。
linkOverlay 無しで、矢印アイコンは後で合成するしかなさそうです。

FromParsingName メソッド → Thumbnail プロパティにて、 Jumbo アイコンを
取得できることは確認しましたが、こちらも単純にはいかず。

殆どのアイコンには透明部があり、時には半透明部も存在するはずですが、
Bitmap プロパティ経由だと背景が Transparent ではなく不透明 Black になってしまう罠。

BitmapSource プロパティで取得すれば、背景を透過した状態で得られますが、
それを WinForm から扱うには、追加の変換が必要になりました。
https://qiita.com/YSRKEN/items/a24bf2173f0129a5825c#bitmapsourcebitmap

Using sf = ShellObject.FromParsingName(lnk)
    Dim tn = sf.Thumbnail
    tn.FormatOption = ShellThumbnailFormatOption.IconOnly
    'bmp = tn.ExtraLargeBitmap 
    bmpSrc = tn.ExtraLargeBitmapSource
End Using


WindowsAPICodePack に頼らず、全部 API で片づければ、
GDI+ や WPF を経由させずに済むのかも知れませんが、
API 実装は面倒なので試していません。

ただ、上記の FromParsingName → Thumbnail を使う方法だと、フォルダーのアイコンを
作成する際に、コンテンツを含んだ Stuffed Folder なアイコンにならないようで、
これもまだエクスプローラーと同じアイコン表示には至っていません。
投稿者 mayopee  (社会人) 投稿日時 2020/6/12 20:58:41
魔界の仮面弁士様、いつもありがとうございます。

>最初の投稿に Microsoft.WindowsAPICodePack の話は出てきてましたっけ。
言葉足らずで、申し訳ありません。Jumboアイコンは取得出来ているという意味で書きました。
WindowsAPICodePack以外にもIImageList::GetIconとIShellItemImageFactory::GetImageも試しています。

>殆どのアイコンには透明部があり、時には半透明部も存在するはずですが、
>Bitmap プロパティ経由だと背景が Transparent ではなく不透明 Black になってしまう罠。
そうなんです。WindowsAPICodePackでBitmapとして取得すると。。。
なのでBitmap取得後に「 bmp.MakeTransparent(Color.Black) 」を入れてました。が、ゴミが残ったり、
消えてほしくない黒が消えたりで、「なんだかなぁ」と思っていました。

魔界の仮面弁士様の投稿を読んで、自分も再度、WindowsAPICodePackでBitmapSourceとして取得してみました。
結果は同様の結果で、背景を透過したきれいな状態で取得できました。
WPF用に用意されたプロパティだと思っていましたがWinFormからでも利用できるのですね。

自分は以下のページを参考にさせていただき,拡張メソッドとして自作ライブラリに登録しました。
(内容はよくわかっていませんが............)
https://www.nuits.jp/entry/2016/10/17/181232

   <Runtime.CompilerServices.Extension>
    Public Function ToBitmap(ByVal bitmapSource As BitmapSource, ByVal pixelFormat As Imaging.PixelFormat) As Bitmap
        Dim width As Integer = bitmapSource.PixelWidth
        Dim height As Integer = bitmapSource.PixelHeight
        Dim stride As Integer = width * ((bitmapSource.Format.BitsPerPixel + 7) \ 8)
        Dim ptr As IntPtr = IntPtr.Zero
        Try
            ptr = Marshal.AllocCoTaskMem(height * stride)
            bitmapSource.CopyPixels(New Windows.Int32Rect(0, 0, width, height), ptr, height * stride, stride)
            Using bitmap = New Bitmap(width, height, stride, pixelFormat, ptr)
                Return New Bitmap(bitmap)
            End Using
        Finally
            If ptr <> IntPtr.Zero Then Marshal.FreeCoTaskMem(ptr)
        End Try
    End Function


>API 実装は面倒なので試していません。
Win32APIやCOMインターフェースをVBから利用するのは面倒ですね。
めっきり使う事はなくなりましたがShell廻りを扱うと、どうしても必要な場面がでてきます。
その点、WindowsAPICodePackのようなライブラリを利用すると便利さがよくわかります。
でも、自分が知らないだけかもしれませんが、WindowsAPICodePackのリファレンスはどこかにあるのでしょうか?
自分はMicrosoftからの提供が終了した後から使っているので、もしご存知なら教えてください。
投稿者 mayopee  (社会人) 投稿日時 2020/6/14 18:13:54
一応、当初の目的である「Jumboアイコンにショートカットを示す矢印を表示する」という事は
達成できたので、解決とさせていただきます。

あれからも、他の方法を試していたのですが、解った事で、これかなというのがあったので、
情報共有の為、残しておきます。

(1)IImageListインターフェースに「SetOverlayImage/GetOverlayImage」という、いかにもというメソッドがある。
(試したのですが、引数の指定方法がわからず、断念しました。)
(2)矢印アイコンはIImageListからも取得できて、Win7ではIndex=4、Win10ではIndex=29に入っている。

これ以上、調査しても時間ばかり消費して、あまり益がないように思うので、これにて打ち止めにしたいと思います。

るきお様、魔界の仮面弁士様、どうもありがとうございました。
投稿者 mayopee  (社会人) 投稿日時 2020/6/25 18:08:02
Stride は、4バイト境界に切り上げられるようで、上のBitmapSource=> Bitmapの変換で
間違いがあったので、訂正しておきます。
https://docs.microsoft.com/ja-jp/dotnet/api/system.drawing.imaging.bitmapdata.stride?view=dotnet-plat-ext-3.1

<Runtime.CompilerServices.Extension>
    Public Function ToBitmap(ByVal bitmapSource As BitmapSource, ByVal pixelFormat As Imaging.PixelFormat) As Bitmap
        Dim width As Integer = bitmapSource.PixelWidth
        Dim height As Integer = bitmapSource.PixelHeight
        Dim stride As Integer = ((width * bitmapSource.Format.BitsPerPixel + 31) \ 32) * 4
        Dim ptr As IntPtr = IntPtr.Zero
        Try
            ptr = Marshal.AllocCoTaskMem(height * stride)
            bitmapSource.CopyPixels(New Windows.Int32Rect(0, 0, width, height), ptr, height * stride, stride)
            Using bitmap = New Bitmap(width, height, stride, pixelFormat, ptr)
                Return New Bitmap(bitmap)
            End Using
        Finally
            If ptr <> IntPtr.Zero Then Marshal.FreeCoTaskMem(ptr)
        End Try
    End Function