Improve zoom window in Viewer

Made a more general mechanism for getting a canvas callback during GUI
rendering, clipped to a region. Use that to implement the zoom window.
The new version has better zoom control (I think), avoids bugs with
clamped image drawing stretching off-canvas, adds a highlight of the
pixel under the cursor, and also prints the RGBA values of that pixel.

Bug: skia:
Change-Id: I2c4da581648e7923c2a6fb28846dfdb52bdd3029
Reviewed-on: https://skia-review.googlesource.com/70723
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
diff --git a/tools/viewer/Viewer.cpp b/tools/viewer/Viewer.cpp
index 6490d63..8ca27fe 100644
--- a/tools/viewer/Viewer.cpp
+++ b/tools/viewer/Viewer.cpp
@@ -1114,6 +1114,15 @@
     ImGui::SetCursorPos(endPos);
 }
 
+typedef std::function<void(SkCanvas*)> CustomGuiPainter;
+static SkTArray<CustomGuiPainter> gCustomGuiPainters;
+
+static void ImGui_Skia_Callback(const ImVec2& size, CustomGuiPainter painter) {
+    intptr_t painterIndex = gCustomGuiPainters.count();
+    gCustomGuiPainters.push_back(painter);
+    ImGui::Image((ImTextureID)painterIndex, size);
+}
+
 void Viewer::drawImGui(SkCanvas* canvas) {
     // Support drawing the ImGui demo window. Superfluous, but gives a good idea of what's possible
     if (fShowImGuiTestWindow) {
@@ -1294,24 +1303,40 @@
         ImGui::End();
     }
 
-    SkPaint zoomImagePaint;
     if (fShowZoomWindow && fLastImage) {
         if (ImGui::Begin("Zoom", &fShowZoomWindow, ImVec2(200, 200))) {
-            static int zoomFactor = 4;
-            ImGui::SliderInt("Scale", &zoomFactor, 1, 16);
+            static int zoomFactor = 8;
+            if (ImGui::Button("<<")) {
+                zoomFactor = SkTMax(zoomFactor / 2, 4);
+            }
+            ImGui::SameLine(); ImGui::Text("%2d", zoomFactor); ImGui::SameLine();
+            if (ImGui::Button(">>")) {
+                zoomFactor = SkTMin(zoomFactor * 2, 32);
+            }
 
-            zoomImagePaint.setShader(fLastImage->makeShader());
-            zoomImagePaint.setColor(SK_ColorWHITE);
-
-            // Zoom by shrinking the corner UVs towards the mouse cursor
             ImVec2 mousePos = ImGui::GetMousePos();
             ImVec2 avail = ImGui::GetContentRegionAvail();
 
-            ImVec2 zoomHalfExtents = ImVec2((avail.x * 0.5f) / zoomFactor,
-                                            (avail.y * 0.5f) / zoomFactor);
-            ImGui::Image(&zoomImagePaint, avail,
-                         ImVec2(mousePos.x - zoomHalfExtents.x, mousePos.y - zoomHalfExtents.y),
-                         ImVec2(mousePos.x + zoomHalfExtents.x, mousePos.y + zoomHalfExtents.y));
+            uint32_t pixel = 0;
+            SkImageInfo info = SkImageInfo::MakeN32Premul(1, 1);
+            if (fLastImage->readPixels(info, &pixel, info.minRowBytes(), mousePos.x, mousePos.y)) {
+                ImGui::SameLine();
+                ImGui::Text("RGBA: %x %x %x %x", SkGetPackedR32(pixel), SkGetPackedG32(pixel),
+                            SkGetPackedB32(pixel), SkGetPackedA32(pixel));
+            }
+
+            ImGui_Skia_Callback(avail, [=](SkCanvas* c) {
+                // Translate so the region of the image that's under the mouse cursor is centered
+                // in the zoom canvas:
+                c->scale(zoomFactor, zoomFactor);
+                c->translate(avail.x * 0.5f / zoomFactor - mousePos.x - 0.5f,
+                             avail.y * 0.5f / zoomFactor - mousePos.y - 0.5f);
+                c->drawImage(this->fLastImage, 0, 0);
+
+                SkPaint outline;
+                outline.setStyle(SkPaint::kStroke_Style);
+                c->drawRect(SkRect::MakeXYWH(mousePos.x, mousePos.y, 1, 1), outline);
+            });
         }
 
         ImGui::End();
@@ -1347,27 +1372,42 @@
         for (int j = 0; j < drawList->CmdBuffer.size(); ++j) {
             const ImDrawCmd* drawCmd = &drawList->CmdBuffer[j];
 
+            SkAutoCanvasRestore acr(canvas, true);
+
             // TODO: Find min/max index for each draw, so we know how many vertices (sigh)
             if (drawCmd->UserCallback) {
                 drawCmd->UserCallback(drawList, drawCmd);
             } else {
-                SkPaint* paint = static_cast<SkPaint*>(drawCmd->TextureId);
-                SkASSERT(paint);
+                intptr_t idIndex = (intptr_t)drawCmd->TextureId;
+                if (idIndex < gCustomGuiPainters.count()) {
+                    // Small image IDs are actually indices into a list of callbacks. We directly
+                    // examing the vertex data to deduce the image rectangle, then reconfigure the
+                    // canvas to be clipped and translated so that the callback code gets to use
+                    // Skia to render a widget in the middle of an ImGui panel.
+                    ImDrawIdx rectIndex = drawList->IdxBuffer[indexOffset];
+                    SkPoint tl = pos[rectIndex], br = pos[rectIndex + 2];
+                    canvas->clipRect(SkRect::MakeLTRB(tl.fX, tl.fY, br.fX, br.fY));
+                    canvas->translate(tl.fX, tl.fY);
+                    gCustomGuiPainters[idIndex](canvas);
+                } else {
+                    SkPaint* paint = static_cast<SkPaint*>(drawCmd->TextureId);
+                    SkASSERT(paint);
 
-                canvas->save();
-                canvas->clipRect(SkRect::MakeLTRB(drawCmd->ClipRect.x, drawCmd->ClipRect.y,
-                                                  drawCmd->ClipRect.z, drawCmd->ClipRect.w));
-                canvas->drawVertices(SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode,
-                                                          drawList->VtxBuffer.size(), pos.begin(),
-                                                          uv.begin(), color.begin(),
-                                                          drawCmd->ElemCount,
-                                                          drawList->IdxBuffer.begin() + indexOffset),
-                                     SkBlendMode::kModulate, *paint);
-                indexOffset += drawCmd->ElemCount;
-                canvas->restore();
+                    canvas->clipRect(SkRect::MakeLTRB(drawCmd->ClipRect.x, drawCmd->ClipRect.y,
+                                                      drawCmd->ClipRect.z, drawCmd->ClipRect.w));
+                    auto vertices = SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode,
+                                                         drawList->VtxBuffer.size(),
+                                                         pos.begin(), uv.begin(), color.begin(),
+                                                         drawCmd->ElemCount,
+                                                         drawList->IdxBuffer.begin() + indexOffset);
+                    canvas->drawVertices(vertices, SkBlendMode::kModulate, *paint);
+                    indexOffset += drawCmd->ElemCount;
+                }
             }
         }
     }
+
+    gCustomGuiPainters.reset();
 }
 
 void Viewer::onIdle() {