Win2d 模糊在 C++ 中并不总是有效

Win2d blur doesn't always work in c++

我正在尝试用 C++ 编写 WP 应用程序,但遇到模糊 .png 图像的奇怪问题。它有时有效,有时无效。当它不起作用时,它看起来好像图像是不可见的。只是为了确保我在 C# 中实现了相同的东西并且它工作得很好。这是我的 C# 代码

private async void blurDefaultAvatar() {
    try {
        var storageFile = await Package.Current.InstalledLocation.GetFileAsync("Assets\menu_user.png");
        using(var imgStream = await storageFile.OpenReadAsync()) {
            using(var stream = await getBlurredImageStreamWithStream(imgStream, CanvasBitmapFileFormat.Png)) {
                var bitmap = new BitmapImage();
                bitmap.SetSource(stream);
                blurredAvatar.Source = bitmap;
            }
        }
    } catch(Exception e) {
        System.Diagnostics.Debug.WriteLine("Avatar load fail: {0}", e.Message);
    }
}

private async Task<IRandomAccessStream> getBlurredImageStreamWithStream(IRandomAccessStream stream, CanvasBitmapFileFormat format) {
    try {
        var device = new CanvasDevice();
        var bitmap = await CanvasBitmap.LoadAsync(device, stream);
        var renderer = new CanvasRenderTarget(device, bitmap.SizeInPixels.Width, bitmap.SizeInPixels.Height, bitmap.Dpi);

        using(var ds = renderer.CreateDrawingSession()) {
            var blur = new GaussianBlurEffect();
            blur.BlurAmount = 30.0f;
            blur.Source = bitmap;
            ds.DrawImage(blur);
        }

        var imgStream = new InMemoryRandomAccessStream();
        await renderer.SaveAsync(imgStream, format);

        return imgStream;
    } catch(Exception e) {
        System.Diagnostics.Debug.WriteLine("Avatar blur fail: {0}", e.Message);
        return null;
    }
}

或多或少(我希望)等同于 c++

void MainPage::blurDefaultAvatar(){
  concurrency::create_task(Package::Current->InstalledLocation->GetFileAsync(L"Assets\menu_user.png")).then([](concurrency::task<StorageFile^> t){
      try{
          auto storageFile = t.get();
          return concurrency::create_task(storageFile->OpenReadAsync());
      } catch(Exception^ e){
          std::wstringstream wss;
          wss<<"\nAvatar not found: '"<<e->Message->Data()<<"'\n";
          OutputDebugString(wss.str().c_str());
          return concurrency::create_task(concurrency::create_async([]()->IRandomAccessStreamWithContentType^{ return nullptr; }));
      }
  }, concurrency::task_continuation_context::use_current()).then([this](concurrency::task<IRandomAccessStreamWithContentType^> t){
      try{
          auto imgStream = t.get();
          concurrency::create_task(getBlurredImageStreamWithStream(imgStream, CanvasBitmapFileFormat::Png)).then([this](IRandomAccessStream^ stream){
              if(stream!=nullptr && stream->Size>0){
                  auto bitmap = ref new BitmapImage();
                  bitmap->SetSource(stream);
                  blurredAvatar->Source = bitmap;
              }
          });
       } catch(Exception^ e){
           std::wstringstream wss;
           wss<<"\nAvatar failed to read: '"<<e->Message->Data()<<"'\n";
           OutputDebugString(wss.str().c_str());
       }
   });
}

IAsyncOperation<IRandomAccessStream^>^ MainPage::getBlurredImageStreamWithStream(IRandomAccessStream^ stream, CanvasBitmapFileFormat format){
    return concurrency::create_async([stream, format]() -> IRandomAccessStream^{
        auto imgStream = ref new InMemoryRandomAccessStream();
        auto device = ref new CanvasDevice();
        return concurrency::create_task(CanvasBitmap::LoadAsync(device, stream)).then([stream, device, format, imgStream](concurrency::task<CanvasBitmap^> t){
            try {
                auto bitmap = t.get();
                auto renderer = ref new CanvasRenderTarget(device, bitmap->SizeInPixels.Width, bitmap->SizeInPixels.Height, bitmap->Dpi);
                auto ds = renderer->CreateDrawingSession();
                auto blur = ref new GaussianBlurEffect();
                blur->BlurAmount = 30.0f;
                blur->Source = bitmap;
                ds->DrawImage(blur);
                return concurrency::create_task(renderer->SaveAsync(imgStream, format));
            } catch(Exception^ e){
                std::wstringstream wss;
                wss<<"\nBitmap load fail: '"<<e->Message->Data()<<"'\n";
                OutputDebugString(wss.str().c_str());
                return concurrency::create_task(concurrency::create_async([]()->void{}));
            }
        }, concurrency::task_continuation_context::use_current()).then([imgStream](concurrency::task<void> t){
            try{
                t.get();
                return imgStream;
            } catch(Exception^ e){
                std::wstringstream wss;
                wss<<"\nStream save fail: '"<<e->Message->Data()<<"'\n";
                OutputDebugString(wss.str().c_str());
                return (InMemoryRandomAccessStream^)nullptr;
            }
        }).get();
    });
}

只需按一下按钮即可调用方法。知道哪里出了问题吗?

您需要关闭()/处置() 绘图会话。所以在 C# 版本中你有:

using(var ds = renderer.CreateDrawingSession()) {
    ...
    ds.DrawImage(blur);
}

离开 using 的范围在绘图会话中调用 Close()。在 C++/CX 中,您使用 "delete ds" 调用 Close()。所以:

auto ds = renderer->CreateDrawingSession();
auto blur = ref new GaussianBlurEffect();
blur->BlurAmount = 30.0f;
blur->Source = bitmap;
ds->DrawImage(blur);
delete ds; // <<<<<---- add this
return concurrency::create_task(renderer->SaveAsync(imgStream, format));

此页面包含有关 'delete' 和 IDisposable 的更多信息。 https://msdn.microsoft.com/en-us/library/windows/apps/xaml/hh699870.aspx

你看到这个有时有效有时无效的原因是当 ds 超出范围时它也会关闭。有时这发生在 SaveAsync 调用获取 D2D 锁之前,有时发生在之后。这里的最终结果是 SaveAsync 要么在绘制模糊之前保存渲染目标的内容,要么在绘制模糊之后保存内容。