aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/svg
diff options
context:
space:
mode:
authorGravatar Alexander Midlash <amidlash@amazon.com>2018-03-06 17:21:28 -0800
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2018-03-07 02:25:30 +0000
commit77e3afc908f5f1e79180beb558b163fcf1cdf8ac (patch)
treecee37923028b99c7a83c00a9eea9e7dda9d98b24 /src/svg
parentc5cf762177c5708a5efa39f95b086c751e03e2e4 (diff)
[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>
Diffstat (limited to 'src/svg')
-rw-r--r--src/svg/SkSVGDevice.cpp168
1 files changed, 162 insertions, 6 deletions
diff --git a/src/svg/SkSVGDevice.cpp b/src/svg/SkSVGDevice.cpp
index 7206709e5c..e3f93cb022 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 @@ private:
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 @@ public:
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 @@ public:
, 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 @@ private:
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 @@ Resources SkSVGDevice::AutoElement::addResources(const MxCp& mc, const SkPaint&
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 @@ void SkSVGDevice::AutoElement::addShaderResources(const SkPaint& paint, Resource
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::drawPoints(SkCanvas::PointMode mode, size_t count,
}
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) {