none
Windows7のAero でのAlphaBlend関数の挙動について RRS feed

  • 質問

  • Windows7環境のVisualBasic6.0(SP6)でAlphaBlend関数を使った半透明と完全透過を同時に行うビットマップ画像合成表示プログラムを作っています。デスクトップ表示モードがクラシックUIのときは半透明と完全透過を同時に行うことが出来たのですが、Aeroにした場合、半透明処理しか行われません。AlphaBlend関数の戻り値はどちらも正常です。

    デスクトップ表示モードがAeroのAlphaBlend関数で半透明と完全透過を同時に行うことは出来ないのでしょうか。デスクトップ表示モードをクラシックUIにすることで問題を回避することも出来ますが、Windows8環境ではクラシックUIの切り替え選択肢もありません。解決策をご教示頂けますでしょうか。

    以下はAlphaBlend関数呼び出しのVB6コードで、AlphaBlend関数のパラメータBlendFunction構造体はAlphaFormat=1、SourceConstantAlpha=128として、32bitDIB(αRGB値)で完全透過色ピクセルのαを0(完全透過)、それ以外の色のαを255(不透明:SourceConstantAlpha=128なので結果は半透明)としています。

    Dim Bld As BlendFunction
    Dim LngBld As Long

    Bld.BlendOp = 0 'AC_SRC_OVER Bld.BlendFlags = 0 '常に0 Bld.SourceConstantAlpha = 128 '半透明 Bld.AlphaFormat = 1 '32bitBMPのαBGR値(完全透過ピクセルではα=0、それ以外は255) MoveMemory LngBld, Bld, Len(LngBld) Ret = AlphaBlend(PictureBox1.hDC, dstX, dstY, dstW, dstH, myBMP.hMemDC, SrcX, SrcY, SrcW, SrcH, LngBld)


    2014年9月22日 13:15

回答

  • だから繰り返し言いますが、GetDIBits()/SetDIBits()を使うのをやめましょう。

    第1引数に指定しているのはピクチャーボックスの互換メモリDCですよね? つまりDDBを経由してしまっています。結局肝心な部分は何も変わっていません。私が提示した、MSDNライブラリのサンプルコードでは、GetDIBits()/SetDIBits()は使わず、DIBのBGRAカラーバッファをpvBits経由で直接書き換えています。私の説明をきちんと読んだ上で、さらにサンプルコードを隅々まで読んで意味を理解した後でコードを書くようにしてください。料理と同様で、慣れないうちから自分で勝手にレシピにないアレンジを加えると失敗するのがプログラミングです。

    なお旧VBでメモリーブロックへのポインタを経由して配列として扱う方法はWebを探せばいくらでも見つかるので説明はそちらに譲りますが、旧VBよりもVC++、VB.NETやVC#のほうが楽です。旧VBはできるかぎり早く捨てましょう。

    Using DIB Sections in VB
    • 編集済み sygh 2014年11月3日 7:50
    • 回答としてマーク Iwa55 2014年11月11日 5:04
    2014年11月2日 17:49

すべての返信

  • 質問の意図がいまいちつかめませんが、Per-Pixelのアルファチャンネルと、AlphaBlend()関数に与えるBLENDFUNCTION構造体のSourceConstantAlphaを合成したいということでしょうか?

    Windows 8.1環境(当然Aero)でVisual Studio 2013のWin32/MFCを使って試してみましたが、普通に下記計算式のとおりに合成できると思います。

    BLENDFUNCTION structure (Windows)

    転送元(Src)となるDIBビットマップのほうを、CreateDIBSection()などを使ってきちんと32bit BGRAフォーマットで作成していますか? また、転送先(Dst)となるピクチャーボックスのDDBを、まず完全不透明色で塗りつぶしていますか?

    Alpha Blending a Bitmap (Windows)

    質問するときは前後の文脈が分かるように、過不足なく情報を公開しましょう。

    なお、アルファブレンドを多用するのであれば、ハードウェアアクセラレートの効くWPF(VC#/VB.NET)やDirect2Dに移行したほうが良いと思います。

    • 編集済み sygh 2014年9月23日 10:05
    • 回答の候補に設定 星 睦美 2014年9月24日 1:57
    2014年9月23日 9:19
  • syghさま

    アドバイスありがとうございます。

    Visual Studio 2013(Win32/MFC)Windows8.1環境での動作検証のほうもありがとうございます。ということは、当方のDIBビットマップの作成に問題があるかもしれません。転送先(Dst)となるピクチャーボックスのDDBは完全不透明色です。転送元(Src)ですが、以下のコードのようになっています。CreateDIBSectionは使っていません。何かお気づきの点がございましたら、アドバイス頂けると幸いです。こちらでも、もう少し調べてみたいと思います。

    'メモリデバイスコンテキスト取得

    hMemDC = CreateCompatibleDC(ピクチャーボックスのhDC) 'ビットマップハンドル取得 hBitmap = CreateCompatibleBitmap(ピクチャーボックスのhDC, Width, Height) '32bitBMP配列確保 Redim BmpImg (Width * Height - 1) 'メモリデバイスコンテキストにビットマップを選択 Ret = SelectObject(hMemDC, hBitmap) '空のビットマップにGetDIBitsで空のビットマップデータを格納 'DIBセクションの設定 BmpInf.bmiHeader.biSize = 40 BmpInf.bmiHeader.biWidth = Width BmpInf.bmiHeader.biHeight = Height BmpInf.bmiHeader.biPlanes = 1 BmpInf.bmiHeader.biBitCount = 32 Ret = GetDIBits(hMemDC, hBitmap, 0, Height, .BmpImg(0), BmpInf, 0) ’ここから32bitBMP配列にBGRA値を書き込み~~~~~ BmpImg(ピクセル座標) = BGRA値 '~~~~~~~~~~~~~~~~~~~~ここまで 'メモリデバイスコンテキストに変更されたビットマップを反映 Ret = SetDIBits(hMemDC, hBitmap, 0, Height, BmpImg(0), BmpInf, 0) 'これ以降が、AlphaBlend関数の呼び出し処理になります。





    • 編集済み Iwa55 2014年9月23日 12:00
    2014年9月23日 11:53
  • CreateCompatibleBitmap()で作成されるのはDIBではなくDDBです。MSDNの説明にも、「デバイスと互換性のあるビットマップを作成する」と説明されています。

    CreateCompatibleBitmap 関数

    DDBはWindowsの画面モード(色深度)などによっても内部フォーマットが変動しうるので、CreateCompatibleBitmap()で生成したDDBが32bit BGRAフォーマットであることを期待するのはNGです。なお、SetDIBits()はDIBデータをDDBに転送するための関数で、転送元DIBと転送先DDBの内部フォーマットの違いを吸収します。

    私が前回提示した、「Alpha Blending a Bitmap」に、CreateDIBSection()を使ったC/C++向けの完全なサンプルコードがそっくりそのまま載っているので、CreateCompatibleBitmap()で作成したDDBをアルファブレンドの転送元に使うのはやめて、まずはそちらを読んでください。

    なお、32bit DIBのカラーチャンネルの並びは、BGRA順になります。リトルエンディアン表記で言えばARGBになります。

     

    ちなみに、繰り返しになりますが、旧VBを使うのはそろそろやめて、VB.NET/VC#+WPFに移行したほうがいいと思います(VBAもVSTOに移行)。.NETやWPFに慣れてしまえば、わざわざ旧VBでWin32 APIを叩くよりもはるかに効率的に開発できます。また、Vista以降でグラフィックスアクセラレーターを活用した、スムーズで応答性の高いUIを構築できるのはWPFやDirect2D/Direct3Dだけです。GDI自体はまだ廃止されることはないと思いますが、すでにメインストリームではなく、旧VBやVBA同様に今後どんどん隅に追いやられていく運命にあるレガシーAPIです。

    • 編集済み sygh 2014年9月23日 16:55
    2014年9月23日 13:35
  • syghさま

    連絡が遅くなり失礼しました。

    CreateCompatibleBitmap()で作成されるのがDIBではなくDDBという事には気づきませんでした。大変勉強になりました。

    ご教示頂いた、「Alpha Blending a Bitmap」のC/C++コードを参考にVB6コードを修正してみたいと思います。

    VB.NET/VC# + WPFでの開発をお勧めされる理由もわかりますが、10年ほど前から積み上げたVB6コードが膨大なため、いまだに移行をためらっております。また、オブジェクト指向プログラミングについて理解が半端であることも理由の一つです。しかし、初心に戻って「習うより慣れろ」の精神で一から始めてみる気持ちが少し湧いてきました。

    とりあえず、VB6での修正コードの動作確認ができましたら、少し遅れますが、ご報告したいと思います。

    2014年9月24日 1:25
  • CreateCompatibleBitmap()をCreateDIBSection()に変更して動作確認をしてみましたが、全く同じ結果でした。

    正方形の左上側を完全透過にしたいのですが、以下のイメージのとおり、Aeroでは背景の色と混ぜた色(=NG)になってしまいます。クラシックUIでは完全透過(=OK)になっております。今回テストした完全なVB6コードも載せますので、よろしければアドバイス頂けないでしょうか。

    結果の比較フォームのデザイン


    '-------------------------------------------------------------
    'フォームモジュール(Form1.frm)
    '-------------------------------------------------------------
    VERSION 5.00
    Object = "{831FDD16-0C5C-11D2-A9FC-0000F8754DA1}#2.1#0"; "MSCOMCTL.OCX"
    Begin VB.Form Form1 
       Caption         =   "AlphaBlendのテスト"
       ClientHeight    =   2400
       ClientLeft      =   120
       ClientTop       =   450
       ClientWidth     =   4560
       LinkTopic       =   "Form1"
       ScaleHeight     =   2400
       ScaleWidth      =   4560
       StartUpPosition =   3  'Windows の既定値
       Begin VB.CheckBox chkTrns 
          Caption         =   "透過あり"
          Height          =   225
          Left            =   3450
          TabIndex        =   6
          Top             =   1020
          Width           =   975
       End
       Begin MSComctlLib.Slider sld 
          Height          =   345
          Left            =   1800
          TabIndex        =   3
          Top             =   930
          Width           =   1275
          _ExtentX        =   2249
          _ExtentY        =   609
          _Version        =   393216
          BorderStyle     =   1
          LargeChange     =   15
          Max             =   255
          SelStart        =   255
          TickFrequency   =   15
          Value           =   255
       End
       Begin VB.OptionButton Opt 
          Caption         =   "CreateCompatibleBitmap使用"
          Height          =   315
          Index           =   1
          Left            =   1800
          TabIndex        =   2
          Top             =   480
          Width           =   2655
       End
       Begin VB.OptionButton Opt 
          Caption         =   "CreateDIBSection使用"
          Height          =   315
          Index           =   0
          Left            =   1800
          TabIndex        =   1
          Top             =   120
          Value           =   -1  'True
          Width           =   2655
       End
       Begin VB.PictureBox Pic 
          AutoSize        =   -1  'True
          Height          =   1560
          Left            =   90
          Picture         =   "Form1.frx":0000
          ScaleHeight     =   100
          ScaleMode       =   3  'ピクセル
          ScaleWidth      =   100
          TabIndex        =   0
          Top             =   90
          Width           =   1560
       End
       Begin VB.Label lbl 
          Caption         =   "青の正方形を半透明と完全透過で合成"
          Height          =   345
          Left            =   150
          TabIndex        =   7
          Top             =   1860
          Width           =   4155
       End
       Begin VB.Label Label2 
          Caption         =   "透明"
          Height          =   195
          Left            =   1740
          TabIndex        =   5
          Top             =   1380
          Width           =   405
       End
       Begin VB.Label Label1 
          Caption         =   "不透明"
          Height          =   195
          Left            =   2580
          TabIndex        =   4
          Top             =   1380
          Width           =   585
       End
    End
    Attribute VB_Name = "Form1"
    Attribute VB_GlobalNameSpace = False
    Attribute VB_Creatable = False
    Attribute VB_PredeclaredId = True
    Attribute VB_Exposed = False
    Option Explicit
    Option Base 0
    
    Private Sub chkTrns_Click()
      sld_Change
    End Sub
    Private Sub sld_Change()
      Const DIB_RGB_COLORS = 0
      Const AC_SRC_OVER = 0
      Const AC_SRC_ALPHA = 1
      
      Dim BmpInf As BITMAPINFO
      Dim pvBits As Long            'CreateDIBSectionのbitmap配列ポインタ
      Dim hMemDC As Long, hBitmap As Long
      Dim Ret As Long
      Dim BMPArray() As Long        'ビットマップ編集バッファ
      Dim biW As Long, biH As Long
      Dim X As Long, Y As Long
      Dim ARGB As Long
      Dim Bld As BlendFunction
      Dim LngBld As Long
      Dim Alpha As Byte
      
      Pic.Cls
      biW = Pic.ScaleWidth
      biH = Pic.ScaleHeight
      'メモリデバイスコンテキストのハンドル取得
      hMemDC = CreateCompatibleDC(Pic.hDC)
      'DIBセクション設定
      BmpInf.bmiHeader.biSize = LenB(BmpInf.bmiHeader)
      BmpInf.bmiHeader.biWidth = biW
      BmpInf.bmiHeader.biHeight = biH
      BmpInf.bmiHeader.biPlanes = 1
      BmpInf.bmiHeader.biBitCount = 32
      If Opt(0).Value = True Then
        'ビットマップのハンドル取得(CreateDIBSection版)
        hBitmap = CreateDIBSection(Pic.hDC, BmpInf, DIB_RGB_COLORS, pvBits, 0, 0)
      Else
        'ビットマップのハンドル取得(CreateCompatibleBitmap版)
        hBitmap = CreateCompatibleBitmap(Pic.hDC, biW, biH)
      End If
      'メモリデバイスコンテキストにビットマップを選択
      Ret = SelectObject(hMemDC, hBitmap)
      '指定されたビットマップのビットを取得し、ビットマップ編集バッファへコピー
      ReDim BMPArray(biW * biH - 1)
      Ret = GetDIBits(hMemDC, hBitmap, 0, biH, BMPArray(0), BmpInf, 0)
      '正方形の対角線を境に半透明と完全透過の青色ビットマップを編集
      For Y = 0 To biH - 1
        For X = 0 To biW - 1
          If X > Y Then
            ARGB = &HFF0000FF       '半透明の青
          Else
            ARGB = &HFF             '完全透過の青
          End If
          BMPArray(biW * Y + X) = ARGB
        Next X
      Next Y
      'メモリデバイスコンテキストに編集したビットマップを反映
      Ret = SetDIBits(hMemDC, hBitmap, 0, Pic.ScaleHeight, BMPArray(0), BmpInf, 0)
      'アルファブレンド
      Alpha = sld.Value
      Bld.BlendOp = AC_SRC_OVER
      Bld.BlendFlags = 0
      Bld.SourceConstantAlpha = Alpha   '半透明
      If chkTrns = 0 Then
        Bld.AlphaFormat = 0             '透過なし
      Else
        Bld.AlphaFormat = AC_SRC_ALPHA  '透過あり
      End If
      MoveMemory LngBld, Bld, Len(LngBld)
      Ret = AlphaBlend(Pic.hDC, 0, 0, Pic.ScaleWidth, Pic.ScaleHeight, hMemDC, 0, 0, Pic.ScaleWidth, Pic.ScaleHeight, LngBld)
      'メモリ解放
      Ret = DeleteDC(hMemDC)
      Ret = DeleteObject(hBitmap)
      ReDim BMPArray(0)
    
    End Sub
    '-------------------------------------------------------------
    '標準モジュール(Module1.bas)
    '-------------------------------------------------------------
    Public Type RGBQUAD                             ' 4 bytes
            rgbBlue As Byte
            rgbGreen As Byte
            rgbRed As Byte
            rgbReserved As Byte
    End Type
    'BITMAPファイルヘッダー
    Public Type BITMAPFILEHEADER  '14 bytes
            bfType As Integer
            bfSize As Long
            bfReserved1 As Integer
            bfReserved2 As Integer
            bfOffBits As Long
    End Type
    'BITMAPインフォヘッダー
    Public Type BITMAPINFOHEADER '40 bytes
            biSize As Long
            biWidth As Long
            biHeight As Long
            biPlanes As Integer
            biBitCount As Integer
            biCompression As Long
            biSizeImage As Long
            biXPelsPerMeter As Long
            biYPelsPerMeter As Long
            biClrUsed As Long
            biClrImportant As Long
    End Type
    'BITMAPインフォ
    Public Type BITMAPINFO
            bmiHeader As BITMAPINFOHEADER
            bmiColors(0 To 255) As RGBQUAD
    End Type
    'ブレンドファンクション構造体
    Public Type BlendFunction
      BlendOp As Byte
      BlendFlags As Byte
      SourceConstantAlpha As Byte
      AlphaFormat As Byte
    End Type
    
    '指定されたデバイスと互換性のあるメモリデバイスコンテキストを作成
    Public Declare Function CreateCompatibleDC Lib "gdi32" (ByVal hDC As Long) As Long
    
    'アプリケーションから直接書き込み可能なデバイス独立のビットマップ(DIB)を作成
    Public Declare Function CreateDIBSection Lib "gdi32" (ByVal hDC As Long, pBitmapInfo As BITMAPINFO, ByVal un As Long, lplpVoid As Long, ByVal handle As Long, ByVal dw As Long) As Long
    
    '指定されたデバイスコンテキストに関連するデバイスと互換性のあるビットマップを作成
    Public Declare Function CreateCompatibleBitmap Lib "gdi32" (ByVal hDC As Long, ByVal nWidth As Long, ByVal nHeight As Long) As Long
    
    '指定されたデバイスコンテキストに、指定されたオブジェクトを選択
    Public Declare Function SelectObject Lib "gdi32" (ByVal hDC As Long, ByVal hObject As Long) As Long
    
    '指定されたビットマップのビットを取得し、指定された形式でバッファにコピー
    Public Declare Function GetDIBits Lib "gdi32" (ByVal aHDC As Long, ByVal hBitmap As Long, ByVal nStartScan As Long, ByVal nNumScans As Long, lpBits As Any, lpBI As BITMAPINFO, ByVal wUsage As Long) As Long
    
    '指定されたデバイス独立ビットマップ (DIB) の色データを使って、ビットマップにピクセルを設定
    Public Declare Function SetDIBits Lib "gdi32" (ByVal hDC As Long, ByVal hBitmap As Long, ByVal nStartScan As Long, ByVal nNumScans As Long, lpBits As Any, lpBI As BITMAPINFO, ByVal wUsage As Long) As Long
    
    'メモリブロックをコピー
    Declare Sub MoveMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
    
    'ビットマップをアルファ値に基づいてコピー
    Public Declare Function AlphaBlend Lib "MSIMG32.dll" (ByVal hdcDest As Long, ByVal nXOriginDest As Long, ByVal nYOriginDest As Long, ByVal nWidthDest As Long, ByVal hHeightDest As Long, ByVal hdcSrc As Long, ByVal nXOriginSrc As Long, ByVal nYOriginSrc As Long, ByVal nWidthSrc As Long, ByVal nHeightSrc As Long, ByVal BlendFunction As Any) As Long
    
    'オブジェクトに関連付けられていたシステムリソースをすべて解放
    Public Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
    
    'デバイスコンテキストを削除
    Public Declare Function DeleteDC Lib "gdi32" (ByVal hDC As Long) As Long
    
    
    


    • 編集済み Iwa55 2014年9月25日 6:54
    2014年9月25日 6:45
  • http://msdn.microsoft.com/ja-jp/library/windows/desktop/dd183393(v=vs.85).aspx

     Note that the APIs use premultiplied alpha, which means that the red, green and blue channel values in the bitmap must be premultiplied with the alpha channel value. For example, if the alpha channel value is x, the red, green and blue channels must be multiplied by x and divided by 0xff prior to the call.

    結局、ここの部分が肝だとおもうのですが。
    完全透明の青が000000FF、というのはpremultipliedではありえず、0になるはずです。


    jzkey

    2014年9月25日 8:16
  • jzkeyさま

    ご返信ありがとうございます。

    おっしゃるとおり、ビットマップのARGB値を&H000000FFから&H00000000にすれば完全透過になりますが、私は、RGB部分の値は保持したまま、Aの部分だけで完全透過をON/OFF制御できないかと考えております。実際のプログラムでは2000*2000ピクセル程度のビットマップを合成表示させようとしており、完全透過のON/OFF制御のたびにRGB部分を毎回計算式で求め直すことを嫌ったからです。仕様であれば仕方がないのですが、解せないのはデスクトップ表示モードをクラシックUIに切り替えた場合に、これが実現できてしまうことです。

    syghさまからも質問の意図がいまいちつかめない旨を頂きましたが、質問の意図は、今述べたことになります。

    • 編集済み Iwa55 2014年9月25日 10:05
    2014年9月25日 9:20
  • だから繰り返し言いますが、GetDIBits()/SetDIBits()を使うのをやめましょう。

    第1引数に指定しているのはピクチャーボックスの互換メモリDCですよね? つまりDDBを経由してしまっています。結局肝心な部分は何も変わっていません。私が提示した、MSDNライブラリのサンプルコードでは、GetDIBits()/SetDIBits()は使わず、DIBのBGRAカラーバッファをpvBits経由で直接書き換えています。私の説明をきちんと読んだ上で、さらにサンプルコードを隅々まで読んで意味を理解した後でコードを書くようにしてください。料理と同様で、慣れないうちから自分で勝手にレシピにないアレンジを加えると失敗するのがプログラミングです。

    なお旧VBでメモリーブロックへのポインタを経由して配列として扱う方法はWebを探せばいくらでも見つかるので説明はそちらに譲りますが、旧VBよりもVC++、VB.NETやVC#のほうが楽です。旧VBはできるかぎり早く捨てましょう。

    Using DIB Sections in VB
    • 編集済み sygh 2014年11月3日 7:50
    • 回答としてマーク Iwa55 2014年11月11日 5:04
    2014年11月2日 17:49
  • syghさま

    連絡が遅くなり失礼しました。

    よく理解しないでコードを書いた私が未熟でした。

    これからはおっしゃるとおり古いVB6を捨てます。

    ありがとうございました。

    2014年11月11日 5:04