[SkSVGDevice] Add support for image shaders.

Below is an example of the generated svg for an image shader
that repeats in the x direction only:

<svg stroke="none" x="9" y="153" width="50" height="30">
  <defs>
    <pattern id="pattern_1_19" patternUnits="userSpaceOnUse" patternContentUnits="userSpaceOnUse" width="31" height="100%" x="0" y="0">
      <image id="img_2_19" x="0" y="0" width="31" height="30" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="data:image/png;base64,LONG_B64_STRING_HERE"></image>
    </pattern>
  </defs>
  <rect fill="url(#pattern_1_19)" stroke="none" x="0" y="0" width="100%" height="100%"></rect>
</svg>

Matching the height of the pattern with the height of the container prevents it from repeating in the y direction.

R=fmalita@chromium.org

Bug: skia::7681
Change-Id: I43e4f19acda4bd40c7a8b5259d67c26a108d6f67
Reviewed-on: https://skia-review.googlesource.com/111420
Commit-Queue: Florin Malita <fmalita@chromium.org>
Reviewed-by: Florin Malita <fmalita@chromium.org>
diff --git a/src/svg/SkSVGDevice.cpp b/src/svg/SkSVGDevice.cpp
index 7206709..e3f93cb 100644
--- a/src/svg/SkSVGDevice.cpp
+++ b/src/svg/SkSVGDevice.cpp
@@ -15,10 +15,13 @@
 #include "SkClipStack.h"
 #include "SkData.h"
 #include "SkDraw.h"
+#include "SkImage.h"
 #include "SkImageEncoder.h"
+#include "SkJpegCodec.h"
 #include "SkPaint.h"
 #include "SkPaintPriv.h"
 #include "SkParsePath.h"
+#include "SkPngCodec.h"
 #include "SkShader.h"
 #include "SkStream.h"
 #include "SkTHash.h"
@@ -245,13 +248,35 @@
     bool     fLastCharWasWhitespace;
 };
 
+// Determine if the paint requires us to reset the viewport.
+// Currently, we do this whenever the paint shader calls
+// for a repeating image.
+bool RequiresViewportReset(const SkPaint& paint) {
+  SkShader* shader = paint.getShader();
+  if (!shader)
+    return false;
+
+  SkShader::TileMode xy[2];
+  SkImage* image = shader->isAImage(nullptr, xy);
+
+  if (!image)
+    return false;
+
+  for (int i = 0; i < 2; i++) {
+    if (xy[i] == SkShader::kRepeat_TileMode)
+      return true;
+  }
+  return false;
 }
 
+}  // namespace
+
 // For now all this does is serve unique serial IDs, but it will eventually evolve to track
 // and deduplicate resources.
 class SkSVGDevice::ResourceBucket : ::SkNoncopyable {
 public:
-    ResourceBucket() : fGradientCount(0), fClipCount(0), fPathCount(0), fImageCount(0) {}
+    ResourceBucket()
+            : fGradientCount(0), fClipCount(0), fPathCount(0), fImageCount(0), fPatternCount(0) {}
 
     SkString addLinearGradient() {
         return SkStringPrintf("gradient_%d", fGradientCount++);
@@ -269,11 +294,16 @@
         return SkStringPrintf("img_%d", fImageCount++);
     }
 
+    SkString addPattern() {
+      return SkStringPrintf("pattern_%d", fPatternCount++);
+    }
+
 private:
     uint32_t fGradientCount;
     uint32_t fClipCount;
     uint32_t fPathCount;
     uint32_t fImageCount;
+    uint32_t fPatternCount;
 };
 
 struct SkSVGDevice::MxCp {
@@ -298,6 +328,7 @@
         , fResourceBucket(bucket) {
 
         Resources res = this->addResources(mc, paint);
+
         if (!res.fClip.isEmpty()) {
             // The clip is in device space. Apply it via a <g> wrapper to avoid local transform
             // interference.
@@ -346,9 +377,16 @@
     Resources addResources(const MxCp&, const SkPaint& paint);
     void addClipResources(const MxCp&, Resources* resources);
     void addShaderResources(const SkPaint& paint, Resources* resources);
+    void addGradientShaderResources(const SkShader* shader, const SkPaint& paint,
+                                    Resources* resources);
+    void addImageShaderResources(const SkShader* shader, const SkPaint& paint,
+                                 Resources* resources);
+
+    void addPatternDef(const SkBitmap& bm);
 
     void addPaint(const SkPaint& paint, const Resources& resources);
 
+
     SkString addLinearGradientDef(const SkShader::GradientInfo& info, const SkShader* shader);
 
     SkXMLWriter*               fWriter;
@@ -423,10 +461,9 @@
     return resources;
 }
 
-void SkSVGDevice::AutoElement::addShaderResources(const SkPaint& paint, Resources* resources) {
-    const SkShader* shader = paint.getShader();
-    SkASSERT(SkToBool(shader));
-
+void SkSVGDevice::AutoElement::addGradientShaderResources(const SkShader* shader,
+                                                          const SkPaint& paint,
+                                                          Resources* resources) {
     SkShader::GradientInfo grInfo;
     grInfo.fColorCount = 0;
     if (SkShader::kLinear_GradientType != shader->asAGradient(&grInfo)) {
@@ -448,6 +485,111 @@
     resources->fPaintServer.printf("url(#%s)", addLinearGradientDef(grInfo, shader).c_str());
 }
 
+// Returns data uri from bytes.
+// it will use any cached data if available, otherwise will
+// encode as png.
+sk_sp<SkData> AsDataUri(SkImage* image) {
+    sk_sp<SkData> imageData = image->encodeToData();
+    if (!imageData) {
+        SkDebugf("Failed to encode image shader's contents.");
+        return nullptr;
+    }
+
+    const char* src = (char*)imageData->data();
+    const char* selectedPrefix = nullptr;
+    size_t selectedPrefixLength = 0;
+
+    const static char pngDataPrefix[] = "data:image/png;base64,";
+    const static char jpgDataPrefix[] = "data:image/jpeg;base64,";
+
+    if (SkJpegCodec::IsJpeg(src, imageData->size())) {
+        selectedPrefix = jpgDataPrefix;
+        selectedPrefixLength = sizeof(jpgDataPrefix);
+    } else {
+      if (!SkPngCodec::IsPng(src, imageData->size())) {
+        SkDebugf("Cached image is stored as unsupported type: %d . re-encoding",
+                 SkCodec::MakeFromData(imageData)->getEncodedFormat());
+        imageData = image->encodeToData(SkEncodedImageFormat::kPNG, 100);
+      }
+      selectedPrefix = pngDataPrefix;
+      selectedPrefixLength = sizeof(pngDataPrefix);
+    }
+
+    size_t b64Size = SkBase64::Encode(imageData->data(), imageData->size(), nullptr);
+    sk_sp<SkData> dataUri = SkData::MakeUninitialized(selectedPrefixLength + b64Size);
+    char* dest = (char*)dataUri->writable_data();
+    memcpy(dest, selectedPrefix, selectedPrefixLength);
+    SkBase64::Encode(imageData->data(), imageData->size(), dest + selectedPrefixLength - 1);
+    dest[dataUri->size() - 1] = 0;
+    return dataUri;
+}
+
+void SkSVGDevice::AutoElement::addImageShaderResources(const SkShader* shader, const SkPaint& paint,
+                                                       Resources* resources) {
+    SkMatrix outMatrix;
+
+    SkShader::TileMode xy[2];
+    SkImage* image = shader->isAImage(&outMatrix, xy);
+    SkASSERT(image);
+
+    SkString patternDims[2];  // width, height
+
+    sk_sp<SkData> dataUri = AsDataUri(image);
+    if (!dataUri) {
+        SkDebugf("Failed to encode data as data URI.");
+        return;
+    }
+    SkIRect imageSize = image->bounds();
+    for (int i = 0; i < 2; i++) {
+        int imageDimension = i == 0 ? imageSize.width() : imageSize.height();
+        switch (xy[i]) {
+            case SkShader::kRepeat_TileMode:
+                patternDims[i].appendScalar(imageDimension);
+            break;
+            default:
+                patternDims[i] = "100%";
+                SkDebugf("unhandled tilemode for %d th dim : %d", i, xy[i]);
+        }
+    }
+
+    SkString patternID = fResourceBucket->addPattern();
+    {
+        AutoElement pattern("pattern", fWriter);
+        pattern.addAttribute("id", patternID);
+        pattern.addAttribute("patternUnits", "userSpaceOnUse");
+        pattern.addAttribute("patternContentUnits", "userSpaceOnUse");
+        pattern.addAttribute("width", patternDims[0]);
+        pattern.addAttribute("height", patternDims[1]);
+        pattern.addAttribute("x", 0);
+        pattern.addAttribute("y", 0);
+
+        {
+            SkString imageID = fResourceBucket->addImage();
+            AutoElement imageTag("image", fWriter);
+            imageTag.addAttribute("id", imageID);
+            imageTag.addAttribute("x", 0);
+            imageTag.addAttribute("y", 0);
+            imageTag.addAttribute("width", image->width());
+            imageTag.addAttribute("height", image->height());
+            imageTag.addAttribute("xlink:href", static_cast<const char*>(dataUri->data()));
+        }
+    }
+    resources->fPaintServer.printf("url(#%s)", patternID.c_str());
+}
+
+void SkSVGDevice::AutoElement::addShaderResources(const SkPaint& paint, Resources* resources) {
+    const SkShader* shader = paint.getShader();
+    SkASSERT(shader);
+
+    if (shader->asAGradient(nullptr) != SkShader::kNone_GradientType) {
+        addGradientShaderResources(shader, paint, resources);
+    } else if (shader->isAImage()) {
+        addImageShaderResources(shader, paint, resources);
+    } else {
+        SkDebugf("unsupported shader type\n");
+    }
+}
+
 void SkSVGDevice::AutoElement::addClipResources(const MxCp& mc, Resources* resources) {
     SkASSERT(!mc.fClipStack->isWideOpen());
 
@@ -680,8 +822,22 @@
 }
 
 void SkSVGDevice::drawRect(const SkRect& r, const SkPaint& paint) {
+    std::unique_ptr<AutoElement> svg;
+    if (RequiresViewportReset(paint)) {
+      svg.reset(new AutoElement("svg", fWriter, fResourceBucket.get(), MxCp(this), paint));
+      svg->addRectAttributes(r);
+    }
+
     AutoElement rect("rect", fWriter, fResourceBucket.get(), MxCp(this), paint);
-    rect.addRectAttributes(r);
+
+    if (svg) {
+      rect.addAttribute("x", 0);
+      rect.addAttribute("y", 0);
+      rect.addAttribute("width", "100%");
+      rect.addAttribute("height", "100%");
+    } else {
+      rect.addRectAttributes(r);
+    }
 }
 
 void SkSVGDevice::drawOval(const SkRect& oval, const SkPaint& paint) {