Support monitor profile in SampleApp (on Windows)

BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2381913003

Review-Url: https://codereview.chromium.org/2381913003
diff --git a/samplecode/SampleApp.cpp b/samplecode/SampleApp.cpp
index 33b38c0..1805ca7 100644
--- a/samplecode/SampleApp.cpp
+++ b/samplecode/SampleApp.cpp
@@ -49,25 +49,22 @@
 class GrContext;
 #endif
 
-const struct {
-    SkColorType         fColorType;
-    bool                fSRGB;
-    const char*         fName;
-} gConfig[] = {
-    { kN32_SkColorType,      false, "L32" },
-    { kN32_SkColorType,       true, "S32" },
-    { kRGBA_F16_SkColorType,  true, "F16" },
+enum OutputColorSpace {
+    kLegacy_OutputColorSpace,
+    kSRGB_OutputColorSpace,
+    kMonitor_OutputColorSpace,
 };
 
-static const char* find_config_name(const SkImageInfo& info) {
-    for (const auto& config : gConfig) {
-        if (config.fColorType == info.colorType() &&
-            config.fSRGB == (info.colorSpace() != nullptr)) {
-            return config.fName;
-        }
-    }
-    return "???";
-}
+const struct {
+    SkColorType         fColorType;
+    OutputColorSpace    fColorSpace;
+    const char*         fName;
+} gConfig[] = {
+    { kN32_SkColorType,      kLegacy_OutputColorSpace,  "L32" },
+    { kN32_SkColorType,      kSRGB_OutputColorSpace,    "S32" },
+    { kRGBA_F16_SkColorType, kSRGB_OutputColorSpace,    "F16" },
+    { kRGBA_F16_SkColorType, kMonitor_OutputColorSpace, "F16 Device" },
+};
 
 // Should be 3x + 1
 #define kMaxFatBitsScale    28
@@ -322,8 +319,20 @@
             kRGBA_F16_SkColorType == win->info().colorType() ||
             fActualColorBits > 24) {
             // We made/have an off-screen surface. Get the contents as an SkImage:
+            SkImageInfo offscreenInfo = win->info();
+            if (kMonitor_OutputColorSpace == gConfig[win->getColorConfigIndex()].fColorSpace) {
+                // This is a big hack. We want our final output to be color "correct". If we snap
+                // an image in the gamut of the monitor, and then render to FBO0 (which we've tagged
+                // as sRGB), then we end up doing round-trip gamut conversion, and still seeing the
+                // same colors on-screen as if we weren't color managed at all.
+                // Instead, we readPixels into a buffer that we claim is sRGB (readPixels doesn't
+                // do gamut conversion), so these pixels then get thrown directly at the monitor,
+                // giving us the expected results (the output is adapted to the monitor's gamut).
+                auto srgb = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
+                offscreenInfo = offscreenInfo.makeColorSpace(srgb);
+            }
             SkBitmap bm;
-            bm.allocPixels(win->info());
+            bm.allocPixels(offscreenInfo);
             renderingCanvas->readPixels(&bm, 0, 0);
             SkPixmap pm;
             bm.peekPixels(&pm);
@@ -815,6 +824,7 @@
 
     fMSAASampleCount = FLAGS_msaa;
     fDeepColor = FLAGS_deepColor;
+    fColorConfigIndex = 0;
 
     if (FLAGS_list) {
         listTitles();
@@ -892,7 +902,11 @@
     int itemID;
 
     itemID = fAppMenu->appendList("ColorType", "ColorType", sinkID, 0,
-                                  gConfig[0].fName, gConfig[1].fName, gConfig[2].fName, nullptr);
+                                  gConfig[0].fName,
+                                  gConfig[1].fName,
+                                  gConfig[2].fName,
+                                  gConfig[3].fName,
+                                  nullptr);
     fAppMenu->assignKeyEquivalentToItem(itemID, 'C');
 
     itemID = fAppMenu->appendList("Device Type", "Device Type", sinkID, 0,
@@ -1566,6 +1580,50 @@
     }
 }
 
+static sk_sp<SkColorSpace> getMonitorColorSpace() {
+#if defined(SK_BUILD_FOR_MAC)
+    CGColorSpaceRef cs = CGDisplayCopyColorSpace(CGMainDisplayID());
+    CFDataRef dataRef = CGColorSpaceCopyICCProfile(cs);
+    const uint8_t* data = CFDataGetBytePtr(dataRef);
+    size_t size = CFDataGetLength(dataRef);
+
+    sk_sp<SkColorSpace> colorSpace = SkColorSpace::NewICC(data, size);
+
+    CFRelease(cs);
+    CFRelease(dataRef);
+    return colorSpace;
+#elif defined(SK_BUILD_FOR_WIN)
+    DISPLAY_DEVICE dd = { sizeof(DISPLAY_DEVICE) };
+
+    // Chrome's code for this currently just gets the primary monitor's profile. This code iterates
+    // over all attached monitors, so it's "better" in that sense. Making intelligent use of this
+    // information (via things like MonitorFromWindow or MonitorFromRect to pick the correct
+    // profile for a particular window or region of a window), is an exercise left to the reader.
+    for (int i = 0; EnumDisplayDevices(NULL, i, &dd, 0); ++i) {
+        if (dd.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) {
+            // There are other helpful things in dd at this point:
+            // dd.DeviceString has a longer name for the adapter
+            // dd.StateFlags indicates primary display, mirroring, etc...
+            HDC dc = CreateDC(NULL, dd.DeviceName, NULL, NULL);
+            if (dc) {
+                char icmPath[MAX_PATH + 1];
+                DWORD pathLength = MAX_PATH;
+                BOOL success = GetICMProfile(dc, &pathLength, icmPath);
+                DeleteDC(dc);
+                if (success) {
+                    sk_sp<SkData> iccData = SkData::MakeFromFileName(icmPath);
+                    return SkColorSpace::NewICC(iccData->data(), iccData->size());
+                }
+            }
+        }
+    }
+
+    return nullptr;
+#else
+    return nullptr;
+#endif
+}
+
 bool SampleWindow::onEvent(const SkEvent& evt) {
     if (evt.isType(gUpdateWindowTitleEvtName)) {
         this->updateTitle();
@@ -1592,12 +1650,29 @@
         return true;
     }
     if (SkOSMenu::FindListIndex(evt, "ColorType", &selected)) {
-        auto colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
+        fColorConfigIndex = selected;
+        sk_sp<SkColorSpace> colorSpace = nullptr;
+        switch (gConfig[selected].fColorSpace) {
+            case kSRGB_OutputColorSpace:
+                colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
+                break;
+            case kMonitor_OutputColorSpace:
+                colorSpace = getMonitorColorSpace();
+                if (!colorSpace) {
+                    // Fallback for platforms / machines where we can't get a monitor profile
+                    colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
+                }
+                break;
+            case kLegacy_OutputColorSpace:
+            default:
+                // Do nothing
+                break;
+        }
         if (kRGBA_F16_SkColorType == gConfig[selected].fColorType) {
+            SkASSERT(colorSpace);
             colorSpace = colorSpace->makeLinearGamma();
         }
-        this->setDeviceColorType(gConfig[selected].fColorType,
-                                 gConfig[selected].fSRGB ? colorSpace : nullptr);
+        this->setDeviceColorType(gConfig[selected].fColorType, colorSpace);
         return true;
     }
     if (SkOSMenu::FindSwitchState(evt, "Slide Show", nullptr)) {
@@ -2121,7 +2196,7 @@
     }
 #endif
 
-    title.appendf(" %s", find_config_name(this->info()));
+    title.appendf(" %s", gConfig[fColorConfigIndex].fName);
 
     if (fDevManager && fDevManager->getColorBits() > 24) {
         title.appendf(" %d bpc", fDevManager->getColorBits());
diff --git a/samplecode/SampleApp.h b/samplecode/SampleApp.h
index 2ac9f05..24e2390 100644
--- a/samplecode/SampleApp.h
+++ b/samplecode/SampleApp.h
@@ -143,6 +143,7 @@
     void postInvalDelay();
 
     DeviceType getDeviceType() const { return fDeviceType; }
+    int getColorConfigIndex() const { return fColorConfigIndex; }
 
 protected:
     void onDraw(SkCanvas* canvas) override;
@@ -219,6 +220,7 @@
 
     int fMSAASampleCount;
     bool fDeepColor;
+    int fColorConfigIndex;
 
     SkScalar fZoomCenterX, fZoomCenterY;