/src/mozilla-central/gfx/2d/DrawTargetSkia.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "DrawTargetSkia.h" |
8 | | #include "SourceSurfaceSkia.h" |
9 | | #include "ScaledFontBase.h" |
10 | | #include "FilterNodeSoftware.h" |
11 | | #include "HelpersSkia.h" |
12 | | |
13 | | #include "mozilla/ArrayUtils.h" |
14 | | #include "mozilla/CheckedInt.h" |
15 | | |
16 | | #include "skia/include/core/SkSurface.h" |
17 | | #include "skia/include/core/SkTypeface.h" |
18 | | #include "skia/include/effects/SkGradientShader.h" |
19 | | #include "skia/include/core/SkColorFilter.h" |
20 | | #include "skia/include/core/SkRegion.h" |
21 | | #include "skia/include/effects/SkBlurImageFilter.h" |
22 | | #include "Blur.h" |
23 | | #include "Logging.h" |
24 | | #include "Tools.h" |
25 | | #include "DataSurfaceHelpers.h" |
26 | | #include "PathHelpers.h" |
27 | | #include "SourceSurfaceCapture.h" |
28 | | #include "Swizzle.h" |
29 | | #include <algorithm> |
30 | | |
31 | | #ifdef USE_SKIA_GPU |
32 | | #include "GLDefs.h" |
33 | | #include "skia/include/gpu/GrContext.h" |
34 | | #include "skia/include/gpu/GrTexture.h" |
35 | | #include "skia/include/gpu/gl/GrGLInterface.h" |
36 | | #endif |
37 | | |
38 | | #ifdef MOZ_WIDGET_COCOA |
39 | | #include "BorrowedContext.h" |
40 | | #include <ApplicationServices/ApplicationServices.h> |
41 | | #include "mozilla/Vector.h" |
42 | | #include "ScaledFontMac.h" |
43 | | #include "CGTextDrawing.h" |
44 | | #endif |
45 | | |
46 | | #ifdef XP_WIN |
47 | | #include "ScaledFontDWrite.h" |
48 | | #endif |
49 | | |
50 | | using namespace std; |
51 | | |
52 | | namespace mozilla { |
53 | | namespace gfx { |
54 | | |
55 | | class GradientStopsSkia : public GradientStops |
56 | | { |
57 | | public: |
58 | | MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GradientStopsSkia, override) |
59 | | |
60 | | GradientStopsSkia(const std::vector<GradientStop>& aStops, uint32_t aNumStops, ExtendMode aExtendMode) |
61 | | : mCount(aNumStops) |
62 | | , mExtendMode(aExtendMode) |
63 | 0 | { |
64 | 0 | if (mCount == 0) { |
65 | 0 | return; |
66 | 0 | } |
67 | 0 | |
68 | 0 | // Skia gradients always require a stop at 0.0 and 1.0, insert these if |
69 | 0 | // we don't have them. |
70 | 0 | uint32_t shift = 0; |
71 | 0 | if (aStops[0].offset != 0) { |
72 | 0 | mCount++; |
73 | 0 | shift = 1; |
74 | 0 | } |
75 | 0 | if (aStops[aNumStops-1].offset != 1) { |
76 | 0 | mCount++; |
77 | 0 | } |
78 | 0 | mColors.resize(mCount); |
79 | 0 | mPositions.resize(mCount); |
80 | 0 | if (aStops[0].offset != 0) { |
81 | 0 | mColors[0] = ColorToSkColor(aStops[0].color, 1.0); |
82 | 0 | mPositions[0] = 0; |
83 | 0 | } |
84 | 0 | for (uint32_t i = 0; i < aNumStops; i++) { |
85 | 0 | mColors[i + shift] = ColorToSkColor(aStops[i].color, 1.0); |
86 | 0 | mPositions[i + shift] = SkFloatToScalar(aStops[i].offset); |
87 | 0 | } |
88 | 0 | if (aStops[aNumStops-1].offset != 1) { |
89 | 0 | mColors[mCount-1] = ColorToSkColor(aStops[aNumStops-1].color, 1.0); |
90 | 0 | mPositions[mCount-1] = SK_Scalar1; |
91 | 0 | } |
92 | 0 | } |
93 | | |
94 | 0 | BackendType GetBackendType() const override { return BackendType::SKIA; } |
95 | | |
96 | | std::vector<SkColor> mColors; |
97 | | std::vector<SkScalar> mPositions; |
98 | | int mCount; |
99 | | ExtendMode mExtendMode; |
100 | | }; |
101 | | |
102 | | /** |
103 | | * When constructing a temporary SkImage via GetSkImageForSurface, we may also |
104 | | * have to construct a temporary DataSourceSurface, which must live as long as |
105 | | * the SkImage. We attach this temporary surface to the image's pixelref, so |
106 | | * that it can be released once the pixelref is freed. |
107 | | */ |
108 | | static void |
109 | | ReleaseTemporarySurface(const void* aPixels, void* aContext) |
110 | 0 | { |
111 | 0 | DataSourceSurface* surf = static_cast<DataSourceSurface*>(aContext); |
112 | 0 | if (surf) { |
113 | 0 | surf->Release(); |
114 | 0 | } |
115 | 0 | } |
116 | | |
117 | | static void |
118 | | WriteRGBXFormat(uint8_t* aData, const IntSize &aSize, |
119 | | const int32_t aStride, SurfaceFormat aFormat) |
120 | 0 | { |
121 | 0 | if (aFormat != SurfaceFormat::B8G8R8X8 || aSize.IsEmpty()) { |
122 | 0 | return; |
123 | 0 | } |
124 | 0 | |
125 | 0 | SwizzleData(aData, aStride, SurfaceFormat::X8R8G8B8_UINT32, |
126 | 0 | aData, aStride, SurfaceFormat::A8R8G8B8_UINT32, |
127 | 0 | aSize); |
128 | 0 | } |
129 | | |
130 | | #ifdef DEBUG |
131 | | static IntRect |
132 | | CalculateSurfaceBounds(const IntSize &aSize, const Rect* aBounds, const Matrix* aMatrix) |
133 | | { |
134 | | IntRect surfaceBounds(IntPoint(0, 0), aSize); |
135 | | if (!aBounds) { |
136 | | return surfaceBounds; |
137 | | } |
138 | | |
139 | | MOZ_ASSERT(aMatrix); |
140 | | Matrix inverse(*aMatrix); |
141 | | if (!inverse.Invert()) { |
142 | | return surfaceBounds; |
143 | | } |
144 | | |
145 | | IntRect bounds; |
146 | | Rect sampledBounds = inverse.TransformBounds(*aBounds); |
147 | | if (!sampledBounds.ToIntRect(&bounds)) { |
148 | | return surfaceBounds; |
149 | | } |
150 | | |
151 | | return surfaceBounds.Intersect(bounds); |
152 | | } |
153 | | |
154 | | static const int kARGBAlphaOffset = SurfaceFormat::A8R8G8B8_UINT32 == SurfaceFormat::B8G8R8A8 ? 3 : 0; |
155 | | |
156 | | static bool |
157 | | VerifyRGBXFormat(uint8_t* aData, const IntSize &aSize, const int32_t aStride, SurfaceFormat aFormat) |
158 | | { |
159 | | if (aFormat != SurfaceFormat::B8G8R8X8 || aSize.IsEmpty()) { |
160 | | return true; |
161 | | } |
162 | | // We should've initialized the data to be opaque already |
163 | | // On debug builds, verify that this is actually true. |
164 | | int height = aSize.height; |
165 | | int width = aSize.width * 4; |
166 | | |
167 | | for (int row = 0; row < height; ++row) { |
168 | | for (int column = 0; column < width; column += 4) { |
169 | | if (aData[column + kARGBAlphaOffset] != 0xFF) { |
170 | | gfxCriticalError() << "RGBX pixel at (" << column << "," << row << ") in " |
171 | | << width << "x" << height << " surface is not opaque: " |
172 | | << int(aData[column]) << "," |
173 | | << int(aData[column+1]) << "," |
174 | | << int(aData[column+2]) << "," |
175 | | << int(aData[column+3]); |
176 | | } |
177 | | } |
178 | | aData += aStride; |
179 | | } |
180 | | |
181 | | return true; |
182 | | } |
183 | | |
184 | | // Since checking every pixel is expensive, this only checks the four corners and center |
185 | | // of a surface that their alpha value is 0xFF. |
186 | | static bool |
187 | | VerifyRGBXCorners(uint8_t* aData, const IntSize &aSize, const int32_t aStride, SurfaceFormat aFormat, const Rect* aBounds = nullptr, const Matrix* aMatrix = nullptr) |
188 | | { |
189 | | if (aFormat != SurfaceFormat::B8G8R8X8 || aSize.IsEmpty()) { |
190 | | return true; |
191 | | } |
192 | | |
193 | | IntRect bounds = CalculateSurfaceBounds(aSize, aBounds, aMatrix); |
194 | | if (bounds.IsEmpty()) { |
195 | | return true; |
196 | | } |
197 | | |
198 | | const int height = bounds.Height(); |
199 | | const int width = bounds.Width(); |
200 | | const int pixelSize = 4; |
201 | | MOZ_ASSERT(aSize.width * pixelSize <= aStride); |
202 | | |
203 | | const int translation = bounds.Y() * aStride + bounds.X() * pixelSize; |
204 | | const int topLeft = translation; |
205 | | const int topRight = topLeft + (width - 1) * pixelSize; |
206 | | const int bottomLeft = translation + (height - 1) * aStride; |
207 | | const int bottomRight = bottomLeft + (width - 1) * pixelSize; |
208 | | |
209 | | // Lastly the center pixel |
210 | | const int middleRowHeight = height / 2; |
211 | | const int middleRowWidth = (width / 2) * pixelSize; |
212 | | const int middle = translation + aStride * middleRowHeight + middleRowWidth; |
213 | | |
214 | | const int offsets[] = { topLeft, topRight, bottomRight, bottomLeft, middle }; |
215 | | for (int offset : offsets) { |
216 | | if (aData[offset + kARGBAlphaOffset] != 0xFF) { |
217 | | int row = offset / aStride; |
218 | | int column = (offset % aStride) / pixelSize; |
219 | | gfxCriticalError() << "RGBX corner pixel at (" << column << "," << row << ") in " |
220 | | << aSize.width << "x" << aSize.height << " surface, bounded by " |
221 | | << "(" << bounds.X() << "," << bounds.Y() << "," << width << "," |
222 | | << height << ") is not opaque: " |
223 | | << int(aData[offset]) << "," |
224 | | << int(aData[offset+1]) << "," |
225 | | << int(aData[offset+2]) << "," |
226 | | << int(aData[offset+3]); |
227 | | } |
228 | | } |
229 | | |
230 | | return true; |
231 | | } |
232 | | #endif |
233 | | |
234 | | static sk_sp<SkImage> |
235 | | GetSkImageForSurface(SourceSurface* aSurface, const Rect* aBounds = nullptr, const Matrix* aMatrix = nullptr) |
236 | 0 | { |
237 | 0 | if (!aSurface) { |
238 | 0 | gfxDebug() << "Creating null Skia image from null SourceSurface"; |
239 | 0 | return nullptr; |
240 | 0 | } |
241 | 0 |
|
242 | 0 | if (aSurface->GetType() == SurfaceType::CAPTURE) { |
243 | 0 | SourceSurfaceCapture* capture = static_cast<SourceSurfaceCapture*>(aSurface); |
244 | 0 | RefPtr<SourceSurface> resolved = capture->Resolve(BackendType::SKIA); |
245 | 0 | if (!resolved) { |
246 | 0 | return nullptr; |
247 | 0 | } |
248 | 0 | MOZ_ASSERT(resolved->GetType() != SurfaceType::CAPTURE); |
249 | 0 | return GetSkImageForSurface(resolved, aBounds, aMatrix); |
250 | 0 | } |
251 | 0 | |
252 | 0 | if (aSurface->GetType() == SurfaceType::SKIA) { |
253 | 0 | return static_cast<SourceSurfaceSkia*>(aSurface)->GetImage(); |
254 | 0 | } |
255 | 0 | |
256 | 0 | DataSourceSurface* surf = aSurface->GetDataSurface().take(); |
257 | 0 | if (!surf) { |
258 | 0 | gfxWarning() << "Failed getting DataSourceSurface for Skia image"; |
259 | 0 | return nullptr; |
260 | 0 | } |
261 | 0 |
|
262 | 0 | SkPixmap pixmap(MakeSkiaImageInfo(surf->GetSize(), surf->GetFormat()), |
263 | 0 | surf->GetData(), surf->Stride()); |
264 | 0 | sk_sp<SkImage> image = SkImage::MakeFromRaster(pixmap, ReleaseTemporarySurface, surf); |
265 | 0 | if (!image) { |
266 | 0 | ReleaseTemporarySurface(nullptr, surf); |
267 | 0 | gfxDebug() << "Failed making Skia raster image for temporary surface"; |
268 | 0 | } |
269 | 0 |
|
270 | 0 | // Skia doesn't support RGBX surfaces so ensure that the alpha value is opaque white. |
271 | 0 | MOZ_ASSERT(VerifyRGBXCorners(surf->GetData(), surf->GetSize(), |
272 | 0 | surf->Stride(), surf->GetFormat(), |
273 | 0 | aBounds, aMatrix)); |
274 | 0 | return image; |
275 | 0 | } |
276 | | |
277 | | DrawTargetSkia::DrawTargetSkia() |
278 | | : mCanvas(nullptr) |
279 | | , mSnapshot(nullptr) |
280 | | , mSnapshotLock{"DrawTargetSkia::mSnapshotLock"} |
281 | | #ifdef MOZ_WIDGET_COCOA |
282 | | , mCG(nullptr) |
283 | | , mColorSpace(nullptr) |
284 | | , mCanvasData(nullptr) |
285 | | , mCGSize(0, 0) |
286 | | , mNeedLayer(false) |
287 | | #endif |
288 | 0 | { |
289 | 0 | } |
290 | | |
291 | | DrawTargetSkia::~DrawTargetSkia() |
292 | 0 | { |
293 | 0 | if (mSnapshot) { |
294 | 0 | MutexAutoLock lock(mSnapshotLock); |
295 | 0 | // We're going to go away, hand our SkSurface to the SourceSurface. |
296 | 0 | mSnapshot->GiveSurface(mSurface); |
297 | 0 | } |
298 | 0 |
|
299 | | #ifdef MOZ_WIDGET_COCOA |
300 | | if (mCG) { |
301 | | CGContextRelease(mCG); |
302 | | mCG = nullptr; |
303 | | } |
304 | | |
305 | | if (mColorSpace) { |
306 | | CGColorSpaceRelease(mColorSpace); |
307 | | mColorSpace = nullptr; |
308 | | } |
309 | | #endif |
310 | | } |
311 | | |
312 | | already_AddRefed<SourceSurface> |
313 | | DrawTargetSkia::Snapshot() |
314 | 0 | { |
315 | 0 | // Without this lock, this could cause us to get out a snapshot and race with |
316 | 0 | // Snapshot::~Snapshot() actually destroying itself. |
317 | 0 | MutexAutoLock lock(mSnapshotLock); |
318 | 0 | RefPtr<SourceSurfaceSkia> snapshot = mSnapshot; |
319 | 0 | if (mSurface && !snapshot) { |
320 | 0 | snapshot = new SourceSurfaceSkia(); |
321 | 0 | sk_sp<SkImage> image; |
322 | 0 | // If the surface is raster, making a snapshot may trigger a pixel copy. |
323 | 0 | // Instead, try to directly make a raster image referencing the surface pixels. |
324 | 0 | SkPixmap pixmap; |
325 | 0 | if (mSurface->peekPixels(&pixmap)) { |
326 | 0 | image = SkImage::MakeFromRaster(pixmap, nullptr, nullptr); |
327 | 0 | } else { |
328 | 0 | image = mSurface->makeImageSnapshot(); |
329 | 0 | } |
330 | 0 | if (!snapshot->InitFromImage(image, mFormat, this)) { |
331 | 0 | return nullptr; |
332 | 0 | } |
333 | 0 | mSnapshot = snapshot; |
334 | 0 | } |
335 | 0 |
|
336 | 0 | return snapshot.forget(); |
337 | 0 | } |
338 | | |
339 | | bool |
340 | | DrawTargetSkia::LockBits(uint8_t** aData, IntSize* aSize, |
341 | | int32_t* aStride, SurfaceFormat* aFormat, |
342 | | IntPoint* aOrigin) |
343 | 0 | { |
344 | 0 | SkImageInfo info; |
345 | 0 | size_t rowBytes; |
346 | 0 | SkIPoint origin; |
347 | 0 | void* pixels = mCanvas->accessTopLayerPixels(&info, &rowBytes, &origin); |
348 | 0 | if (!pixels || |
349 | 0 | // Ensure the layer is at the origin if required. |
350 | 0 | (!aOrigin && !origin.isZero())) { |
351 | 0 | return false; |
352 | 0 | } |
353 | 0 | |
354 | 0 | MarkChanged(); |
355 | 0 |
|
356 | 0 | *aData = reinterpret_cast<uint8_t*>(pixels); |
357 | 0 | *aSize = IntSize(info.width(), info.height()); |
358 | 0 | *aStride = int32_t(rowBytes); |
359 | 0 | *aFormat = SkiaColorTypeToGfxFormat(info.colorType(), info.alphaType()); |
360 | 0 | if (aOrigin) { |
361 | 0 | *aOrigin = IntPoint(origin.x(), origin.y()); |
362 | 0 | } |
363 | 0 | return true; |
364 | 0 | } |
365 | | |
366 | | void |
367 | | DrawTargetSkia::ReleaseBits(uint8_t* aData) |
368 | 0 | { |
369 | 0 | } |
370 | | |
371 | | static void |
372 | | ReleaseImage(const void* aPixels, void* aContext) |
373 | 0 | { |
374 | 0 | SkImage* image = static_cast<SkImage*>(aContext); |
375 | 0 | SkSafeUnref(image); |
376 | 0 | } |
377 | | |
378 | | static sk_sp<SkImage> |
379 | | ExtractSubset(sk_sp<SkImage> aImage, const IntRect& aRect) |
380 | 0 | { |
381 | 0 | SkIRect subsetRect = IntRectToSkIRect(aRect); |
382 | 0 | if (aImage->bounds() == subsetRect) { |
383 | 0 | return aImage; |
384 | 0 | } |
385 | 0 | // makeSubset is slow, so prefer to use SkPixmap::extractSubset where possible. |
386 | 0 | SkPixmap pixmap, subsetPixmap; |
387 | 0 | if (aImage->peekPixels(&pixmap) && |
388 | 0 | pixmap.extractSubset(&subsetPixmap, subsetRect)) { |
389 | 0 | // Release the original image reference so only the subset image keeps it alive. |
390 | 0 | return SkImage::MakeFromRaster(subsetPixmap, ReleaseImage, aImage.release()); |
391 | 0 | } |
392 | 0 | return aImage->makeSubset(subsetRect); |
393 | 0 | } |
394 | | |
395 | | static void |
396 | | FreeBitmapPixels(void* aBuf, void*) |
397 | 0 | { |
398 | 0 | sk_free(aBuf); |
399 | 0 | } |
400 | | |
401 | | static bool |
402 | | ExtractAlphaBitmap(const sk_sp<SkImage>& aImage, SkBitmap* aResultBitmap) |
403 | 0 | { |
404 | 0 | SkImageInfo info = SkImageInfo::MakeA8(aImage->width(), aImage->height()); |
405 | 0 | // Skia does not fully allocate the last row according to stride. |
406 | 0 | // Since some of our algorithms (i.e. blur) depend on this, we must allocate |
407 | 0 | // the bitmap pixels manually. |
408 | 0 | size_t stride = SkAlign4(info.minRowBytes()); |
409 | 0 | CheckedInt<size_t> size = stride; |
410 | 0 | size *= info.height(); |
411 | 0 | if (size.isValid()) { |
412 | 0 | void* buf = sk_malloc_flags(size.value(), 0); |
413 | 0 | if (buf) { |
414 | 0 | SkBitmap bitmap; |
415 | 0 | if (bitmap.installPixels(info, buf, stride, FreeBitmapPixels, nullptr) && |
416 | 0 | aImage->readPixels(bitmap.info(), bitmap.getPixels(), bitmap.rowBytes(), 0, 0)) { |
417 | 0 | *aResultBitmap = bitmap; |
418 | 0 | return true; |
419 | 0 | } |
420 | 0 | } |
421 | 0 | } |
422 | 0 | |
423 | 0 | gfxWarning() << "Failed reading alpha pixels for Skia bitmap"; |
424 | 0 | return false; |
425 | 0 | } |
426 | | |
427 | | static sk_sp<SkImage> |
428 | | ExtractAlphaForSurface(SourceSurface* aSurface) |
429 | 0 | { |
430 | 0 | sk_sp<SkImage> image = GetSkImageForSurface(aSurface); |
431 | 0 | if (!image) { |
432 | 0 | return nullptr; |
433 | 0 | } |
434 | 0 | if (image->isAlphaOnly()) { |
435 | 0 | return image; |
436 | 0 | } |
437 | 0 | |
438 | 0 | SkBitmap bitmap; |
439 | 0 | if (!ExtractAlphaBitmap(image, &bitmap)) { |
440 | 0 | return nullptr; |
441 | 0 | } |
442 | 0 | |
443 | 0 | // Mark the bitmap immutable so that it will be shared rather than copied. |
444 | 0 | bitmap.setImmutable(); |
445 | 0 | return SkImage::MakeFromBitmap(bitmap); |
446 | 0 | } |
447 | | |
448 | | static void |
449 | | SetPaintPattern(SkPaint& aPaint, const Pattern& aPattern, Float aAlpha = 1.0, const SkMatrix* aMatrix = nullptr, const Rect* aBounds = nullptr) |
450 | 0 | { |
451 | 0 | switch (aPattern.GetType()) { |
452 | 0 | case PatternType::COLOR: { |
453 | 0 | Color color = static_cast<const ColorPattern&>(aPattern).mColor; |
454 | 0 | aPaint.setColor(ColorToSkColor(color, aAlpha)); |
455 | 0 | break; |
456 | 0 | } |
457 | 0 | case PatternType::LINEAR_GRADIENT: { |
458 | 0 | const LinearGradientPattern& pat = static_cast<const LinearGradientPattern&>(aPattern); |
459 | 0 | GradientStopsSkia *stops = static_cast<GradientStopsSkia*>(pat.mStops.get()); |
460 | 0 | if (!stops || stops->mCount < 2 || |
461 | 0 | !pat.mBegin.IsFinite() || !pat.mEnd.IsFinite()) { |
462 | 0 | aPaint.setColor(SK_ColorTRANSPARENT); |
463 | 0 | } else { |
464 | 0 | SkShader::TileMode mode = ExtendModeToTileMode(stops->mExtendMode, Axis::BOTH); |
465 | 0 | SkPoint points[2]; |
466 | 0 | points[0] = SkPoint::Make(SkFloatToScalar(pat.mBegin.x), SkFloatToScalar(pat.mBegin.y)); |
467 | 0 | points[1] = SkPoint::Make(SkFloatToScalar(pat.mEnd.x), SkFloatToScalar(pat.mEnd.y)); |
468 | 0 |
|
469 | 0 | SkMatrix mat; |
470 | 0 | GfxMatrixToSkiaMatrix(pat.mMatrix, mat); |
471 | 0 | if (aMatrix) { |
472 | 0 | mat.postConcat(*aMatrix); |
473 | 0 | } |
474 | 0 | sk_sp<SkShader> shader = SkGradientShader::MakeLinear(points, |
475 | 0 | &stops->mColors.front(), |
476 | 0 | &stops->mPositions.front(), |
477 | 0 | stops->mCount, |
478 | 0 | mode, 0, &mat); |
479 | 0 | if (shader) { |
480 | 0 | aPaint.setShader(shader); |
481 | 0 | } else { |
482 | 0 | aPaint.setColor(SK_ColorTRANSPARENT); |
483 | 0 | } |
484 | 0 | } |
485 | 0 | break; |
486 | 0 | } |
487 | 0 | case PatternType::RADIAL_GRADIENT: { |
488 | 0 | const RadialGradientPattern& pat = static_cast<const RadialGradientPattern&>(aPattern); |
489 | 0 | GradientStopsSkia *stops = static_cast<GradientStopsSkia*>(pat.mStops.get()); |
490 | 0 | if (!stops || stops->mCount < 2 || |
491 | 0 | !pat.mCenter1.IsFinite() || !IsFinite(pat.mRadius1) || |
492 | 0 | !pat.mCenter2.IsFinite() || !IsFinite(pat.mRadius2)) { |
493 | 0 | aPaint.setColor(SK_ColorTRANSPARENT); |
494 | 0 | } else { |
495 | 0 | SkShader::TileMode mode = ExtendModeToTileMode(stops->mExtendMode, Axis::BOTH); |
496 | 0 | SkPoint points[2]; |
497 | 0 | points[0] = SkPoint::Make(SkFloatToScalar(pat.mCenter1.x), SkFloatToScalar(pat.mCenter1.y)); |
498 | 0 | points[1] = SkPoint::Make(SkFloatToScalar(pat.mCenter2.x), SkFloatToScalar(pat.mCenter2.y)); |
499 | 0 |
|
500 | 0 | SkMatrix mat; |
501 | 0 | GfxMatrixToSkiaMatrix(pat.mMatrix, mat); |
502 | 0 | if (aMatrix) { |
503 | 0 | mat.postConcat(*aMatrix); |
504 | 0 | } |
505 | 0 | sk_sp<SkShader> shader = SkGradientShader::MakeTwoPointConical(points[0], |
506 | 0 | SkFloatToScalar(pat.mRadius1), |
507 | 0 | points[1], |
508 | 0 | SkFloatToScalar(pat.mRadius2), |
509 | 0 | &stops->mColors.front(), |
510 | 0 | &stops->mPositions.front(), |
511 | 0 | stops->mCount, |
512 | 0 | mode, 0, &mat); |
513 | 0 | if (shader) { |
514 | 0 | aPaint.setShader(shader); |
515 | 0 | } else { |
516 | 0 | aPaint.setColor(SK_ColorTRANSPARENT); |
517 | 0 | } |
518 | 0 | } |
519 | 0 | break; |
520 | 0 | } |
521 | 0 | case PatternType::SURFACE: { |
522 | 0 | const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern); |
523 | 0 | sk_sp<SkImage> image = GetSkImageForSurface(pat.mSurface, aBounds, &pat.mMatrix); |
524 | 0 | if (!image) { |
525 | 0 | aPaint.setColor(SK_ColorTRANSPARENT); |
526 | 0 | break; |
527 | 0 | } |
528 | 0 |
|
529 | 0 | SkMatrix mat; |
530 | 0 | GfxMatrixToSkiaMatrix(pat.mMatrix, mat); |
531 | 0 | if (aMatrix) { |
532 | 0 | mat.postConcat(*aMatrix); |
533 | 0 | } |
534 | 0 |
|
535 | 0 | if (!pat.mSamplingRect.IsEmpty()) { |
536 | 0 | image = ExtractSubset(image, pat.mSamplingRect); |
537 | 0 | mat.preTranslate(pat.mSamplingRect.X(), pat.mSamplingRect.Y()); |
538 | 0 | } |
539 | 0 |
|
540 | 0 | SkShader::TileMode xTileMode = ExtendModeToTileMode(pat.mExtendMode, Axis::X_AXIS); |
541 | 0 | SkShader::TileMode yTileMode = ExtendModeToTileMode(pat.mExtendMode, Axis::Y_AXIS); |
542 | 0 |
|
543 | 0 | aPaint.setShader(image->makeShader(xTileMode, yTileMode, &mat)); |
544 | 0 |
|
545 | 0 | if (pat.mSamplingFilter == SamplingFilter::POINT) { |
546 | 0 | aPaint.setFilterQuality(kNone_SkFilterQuality); |
547 | 0 | } |
548 | 0 | break; |
549 | 0 | } |
550 | 0 | } |
551 | 0 | } |
552 | | |
553 | | static inline Rect |
554 | | GetClipBounds(SkCanvas *aCanvas) |
555 | 0 | { |
556 | 0 | // Use a manually transformed getClipDeviceBounds instead of |
557 | 0 | // getClipBounds because getClipBounds inflates the the bounds |
558 | 0 | // by a pixel in each direction to compensate for antialiasing. |
559 | 0 | SkIRect deviceBounds; |
560 | 0 | if (!aCanvas->getDeviceClipBounds(&deviceBounds)) { |
561 | 0 | return Rect(); |
562 | 0 | } |
563 | 0 | SkMatrix inverseCTM; |
564 | 0 | if (!aCanvas->getTotalMatrix().invert(&inverseCTM)) { |
565 | 0 | return Rect(); |
566 | 0 | } |
567 | 0 | SkRect localBounds; |
568 | 0 | inverseCTM.mapRect(&localBounds, SkRect::Make(deviceBounds)); |
569 | 0 | return SkRectToRect(localBounds); |
570 | 0 | } |
571 | | |
572 | | struct AutoPaintSetup { |
573 | | AutoPaintSetup(SkCanvas *aCanvas, const DrawOptions& aOptions, const Pattern& aPattern, const Rect* aMaskBounds = nullptr, const SkMatrix* aMatrix = nullptr, const Rect* aSourceBounds = nullptr) |
574 | | : mNeedsRestore(false), mAlpha(1.0) |
575 | 0 | { |
576 | 0 | Init(aCanvas, aOptions, aMaskBounds, false); |
577 | 0 | SetPaintPattern(mPaint, aPattern, mAlpha, aMatrix, aSourceBounds); |
578 | 0 | } |
579 | | |
580 | | AutoPaintSetup(SkCanvas *aCanvas, const DrawOptions& aOptions, const Rect* aMaskBounds = nullptr, bool aForceGroup = false) |
581 | | : mNeedsRestore(false), mAlpha(1.0) |
582 | 0 | { |
583 | 0 | Init(aCanvas, aOptions, aMaskBounds, aForceGroup); |
584 | 0 | } |
585 | | |
586 | | ~AutoPaintSetup() |
587 | 0 | { |
588 | 0 | if (mNeedsRestore) { |
589 | 0 | mCanvas->restore(); |
590 | 0 | } |
591 | 0 | } |
592 | | |
593 | | void Init(SkCanvas *aCanvas, const DrawOptions& aOptions, const Rect* aMaskBounds, bool aForceGroup) |
594 | 0 | { |
595 | 0 | mPaint.setBlendMode(GfxOpToSkiaOp(aOptions.mCompositionOp)); |
596 | 0 | mCanvas = aCanvas; |
597 | 0 |
|
598 | 0 | //TODO: Can we set greyscale somehow? |
599 | 0 | if (aOptions.mAntialiasMode != AntialiasMode::NONE) { |
600 | 0 | mPaint.setAntiAlias(true); |
601 | 0 | } else { |
602 | 0 | mPaint.setAntiAlias(false); |
603 | 0 | } |
604 | 0 |
|
605 | 0 | bool needsGroup = aForceGroup || |
606 | 0 | (!IsOperatorBoundByMask(aOptions.mCompositionOp) && |
607 | 0 | (!aMaskBounds || !aMaskBounds->Contains(GetClipBounds(aCanvas)))); |
608 | 0 |
|
609 | 0 | // TODO: We could skip the temporary for operator_source and just |
610 | 0 | // clear the clip rect. The other operators would be harder |
611 | 0 | // but could be worth it to skip pushing a group. |
612 | 0 | if (needsGroup) { |
613 | 0 | mPaint.setBlendMode(SkBlendMode::kSrcOver); |
614 | 0 | SkPaint temp; |
615 | 0 | temp.setBlendMode(GfxOpToSkiaOp(aOptions.mCompositionOp)); |
616 | 0 | temp.setAlpha(ColorFloatToByte(aOptions.mAlpha)); |
617 | 0 | //TODO: Get a rect here |
618 | 0 | mCanvas->saveLayerPreserveLCDTextRequests(nullptr, &temp); |
619 | 0 | mNeedsRestore = true; |
620 | 0 | } else { |
621 | 0 | mPaint.setAlpha(ColorFloatToByte(aOptions.mAlpha)); |
622 | 0 | mAlpha = aOptions.mAlpha; |
623 | 0 | } |
624 | 0 | mPaint.setFilterQuality(kLow_SkFilterQuality); |
625 | 0 | } |
626 | | |
627 | | // TODO: Maybe add an operator overload to access this easier? |
628 | | SkPaint mPaint; |
629 | | bool mNeedsRestore; |
630 | | SkCanvas* mCanvas; |
631 | | Float mAlpha; |
632 | | }; |
633 | | |
634 | | void |
635 | | DrawTargetSkia::Flush() |
636 | 0 | { |
637 | 0 | mCanvas->flush(); |
638 | 0 | } |
639 | | |
640 | | void |
641 | | DrawTargetSkia::DrawSurface(SourceSurface *aSurface, |
642 | | const Rect &aDest, |
643 | | const Rect &aSource, |
644 | | const DrawSurfaceOptions &aSurfOptions, |
645 | | const DrawOptions &aOptions) |
646 | 0 | { |
647 | 0 | if (aSource.IsEmpty()) { |
648 | 0 | return; |
649 | 0 | } |
650 | 0 | |
651 | 0 | MarkChanged(); |
652 | 0 |
|
653 | 0 | sk_sp<SkImage> image = GetSkImageForSurface(aSurface); |
654 | 0 | if (!image) { |
655 | 0 | return; |
656 | 0 | } |
657 | 0 | |
658 | 0 | SkRect destRect = RectToSkRect(aDest); |
659 | 0 | SkRect sourceRect = RectToSkRect(aSource); |
660 | 0 | bool forceGroup = image->isAlphaOnly() && |
661 | 0 | aOptions.mCompositionOp != CompositionOp::OP_OVER; |
662 | 0 |
|
663 | 0 | AutoPaintSetup paint(mCanvas, aOptions, &aDest, forceGroup); |
664 | 0 | if (aSurfOptions.mSamplingFilter == SamplingFilter::POINT) { |
665 | 0 | paint.mPaint.setFilterQuality(kNone_SkFilterQuality); |
666 | 0 | } |
667 | 0 |
|
668 | 0 | mCanvas->drawImageRect(image, sourceRect, destRect, &paint.mPaint); |
669 | 0 | } |
670 | | |
671 | | DrawTargetType |
672 | | DrawTargetSkia::GetType() const |
673 | 0 | { |
674 | 0 | #ifdef USE_SKIA_GPU |
675 | 0 | if (mGrContext) { |
676 | 0 | return DrawTargetType::HARDWARE_RASTER; |
677 | 0 | } |
678 | 0 | #endif |
679 | 0 | return DrawTargetType::SOFTWARE_RASTER; |
680 | 0 | } |
681 | | |
682 | | void |
683 | | DrawTargetSkia::DrawFilter(FilterNode *aNode, |
684 | | const Rect &aSourceRect, |
685 | | const Point &aDestPoint, |
686 | | const DrawOptions &aOptions) |
687 | 0 | { |
688 | 0 | FilterNodeSoftware* filter = static_cast<FilterNodeSoftware*>(aNode); |
689 | 0 | filter->Draw(this, aSourceRect, aDestPoint, aOptions); |
690 | 0 | } |
691 | | |
692 | | void |
693 | | DrawTargetSkia::DrawSurfaceWithShadow(SourceSurface *aSurface, |
694 | | const Point &aDest, |
695 | | const Color &aColor, |
696 | | const Point &aOffset, |
697 | | Float aSigma, |
698 | | CompositionOp aOperator) |
699 | 0 | { |
700 | 0 | if (aSurface->GetSize().IsEmpty()) { |
701 | 0 | return; |
702 | 0 | } |
703 | 0 | |
704 | 0 | MarkChanged(); |
705 | 0 |
|
706 | 0 | sk_sp<SkImage> image = GetSkImageForSurface(aSurface); |
707 | 0 | if (!image) { |
708 | 0 | return; |
709 | 0 | } |
710 | 0 | |
711 | 0 | mCanvas->save(); |
712 | 0 | mCanvas->resetMatrix(); |
713 | 0 |
|
714 | 0 | SkPaint paint; |
715 | 0 | paint.setBlendMode(GfxOpToSkiaOp(aOperator)); |
716 | 0 |
|
717 | 0 | // bug 1201272 |
718 | 0 | // We can't use the SkDropShadowImageFilter here because it applies the xfer |
719 | 0 | // mode first to render the bitmap to a temporary layer, and then implicitly |
720 | 0 | // uses src-over to composite the resulting shadow. |
721 | 0 | // The canvas spec, however, states that the composite op must be used to |
722 | 0 | // composite the resulting shadow, so we must instead use a SkBlurImageFilter |
723 | 0 | // to blur the image ourselves. |
724 | 0 |
|
725 | 0 | SkPaint shadowPaint; |
726 | 0 | shadowPaint.setBlendMode(GfxOpToSkiaOp(aOperator)); |
727 | 0 |
|
728 | 0 | auto shadowDest = IntPoint::Round(aDest + aOffset); |
729 | 0 |
|
730 | 0 | SkBitmap blurMask; |
731 | 0 | if (!UsingSkiaGPU() && |
732 | 0 | ExtractAlphaBitmap(image, &blurMask)) { |
733 | 0 | // Prefer using our own box blur instead of Skia's when we're |
734 | 0 | // not using the GPU. It currently performs much better than |
735 | 0 | // SkBlurImageFilter or SkBlurMaskFilter on the CPU. |
736 | 0 | AlphaBoxBlur blur(Rect(0, 0, blurMask.width(), blurMask.height()), |
737 | 0 | int32_t(blurMask.rowBytes()), |
738 | 0 | aSigma, aSigma); |
739 | 0 | blur.Blur(reinterpret_cast<uint8_t*>(blurMask.getPixels())); |
740 | 0 | blurMask.notifyPixelsChanged(); |
741 | 0 |
|
742 | 0 | shadowPaint.setColor(ColorToSkColor(aColor, 1.0f)); |
743 | 0 |
|
744 | 0 | mCanvas->drawBitmap(blurMask, shadowDest.x, shadowDest.y, &shadowPaint); |
745 | 0 | } else { |
746 | 0 | sk_sp<SkImageFilter> blurFilter(SkBlurImageFilter::Make(aSigma, aSigma, nullptr)); |
747 | 0 | sk_sp<SkColorFilter> colorFilter( |
748 | 0 | SkColorFilter::MakeModeFilter(ColorToSkColor(aColor, 1.0f), SkBlendMode::kSrcIn)); |
749 | 0 |
|
750 | 0 | shadowPaint.setImageFilter(blurFilter); |
751 | 0 | shadowPaint.setColorFilter(colorFilter); |
752 | 0 |
|
753 | 0 | mCanvas->drawImage(image, shadowDest.x, shadowDest.y, &shadowPaint); |
754 | 0 | } |
755 | 0 |
|
756 | 0 | if (aSurface->GetFormat() != SurfaceFormat::A8) { |
757 | 0 | // Composite the original image after the shadow |
758 | 0 | auto dest = IntPoint::Round(aDest); |
759 | 0 | mCanvas->drawImage(image, dest.x, dest.y, &paint); |
760 | 0 | } |
761 | 0 |
|
762 | 0 | mCanvas->restore(); |
763 | 0 | } |
764 | | |
765 | | void |
766 | | DrawTargetSkia::FillRect(const Rect &aRect, |
767 | | const Pattern &aPattern, |
768 | | const DrawOptions &aOptions) |
769 | 0 | { |
770 | 0 | // The sprite blitting path in Skia can be faster than the shader blitter for |
771 | 0 | // operators other than source (or source-over with opaque surface). So, when |
772 | 0 | // possible/beneficial, route to DrawSurface which will use the sprite blitter. |
773 | 0 | if (aPattern.GetType() == PatternType::SURFACE && |
774 | 0 | aOptions.mCompositionOp != CompositionOp::OP_SOURCE) { |
775 | 0 | const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern); |
776 | 0 | // Verify there is a valid surface and a pattern matrix without skew. |
777 | 0 | if (pat.mSurface && |
778 | 0 | (aOptions.mCompositionOp != CompositionOp::OP_OVER || |
779 | 0 | GfxFormatToSkiaAlphaType(pat.mSurface->GetFormat()) != kOpaque_SkAlphaType) && |
780 | 0 | !pat.mMatrix.HasNonAxisAlignedTransform()) { |
781 | 0 | // Bound the sampling to smaller of the bounds or the sampling rect. |
782 | 0 | IntRect srcRect(IntPoint(0, 0), pat.mSurface->GetSize()); |
783 | 0 | if (!pat.mSamplingRect.IsEmpty()) { |
784 | 0 | srcRect = srcRect.Intersect(pat.mSamplingRect); |
785 | 0 | } |
786 | 0 | // Transform the destination rectangle by the inverse of the pattern |
787 | 0 | // matrix so that it is in pattern space like the source rectangle. |
788 | 0 | Rect patRect = aRect - pat.mMatrix.GetTranslation(); |
789 | 0 | patRect.Scale(1.0f / pat.mMatrix._11, 1.0f / pat.mMatrix._22); |
790 | 0 | // Verify the pattern rectangle will not tile or clamp. |
791 | 0 | if (!patRect.IsEmpty() && srcRect.Contains(RoundedOut(patRect))) { |
792 | 0 | // The pattern is a surface with an axis-aligned source rectangle |
793 | 0 | // fitting entirely in its bounds, so just treat it as a DrawSurface. |
794 | 0 | DrawSurface(pat.mSurface, aRect, patRect, |
795 | 0 | DrawSurfaceOptions(pat.mSamplingFilter), |
796 | 0 | aOptions); |
797 | 0 | return; |
798 | 0 | } |
799 | 0 | } |
800 | 0 | } |
801 | 0 | |
802 | 0 | MarkChanged(); |
803 | 0 | SkRect rect = RectToSkRect(aRect); |
804 | 0 | AutoPaintSetup paint(mCanvas, aOptions, aPattern, &aRect, nullptr, &aRect); |
805 | 0 |
|
806 | 0 | mCanvas->drawRect(rect, paint.mPaint); |
807 | 0 | } |
808 | | |
809 | | void |
810 | | DrawTargetSkia::Stroke(const Path *aPath, |
811 | | const Pattern &aPattern, |
812 | | const StrokeOptions &aStrokeOptions, |
813 | | const DrawOptions &aOptions) |
814 | 0 | { |
815 | 0 | MarkChanged(); |
816 | 0 | MOZ_ASSERT(aPath, "Null path"); |
817 | 0 | if (aPath->GetBackendType() != BackendType::SKIA) { |
818 | 0 | return; |
819 | 0 | } |
820 | 0 | |
821 | 0 | const PathSkia *skiaPath = static_cast<const PathSkia*>(aPath); |
822 | 0 |
|
823 | 0 |
|
824 | 0 | AutoPaintSetup paint(mCanvas, aOptions, aPattern); |
825 | 0 | if (!StrokeOptionsToPaint(paint.mPaint, aStrokeOptions)) { |
826 | 0 | return; |
827 | 0 | } |
828 | 0 | |
829 | 0 | if (!skiaPath->GetPath().isFinite()) { |
830 | 0 | return; |
831 | 0 | } |
832 | 0 | |
833 | 0 | mCanvas->drawPath(skiaPath->GetPath(), paint.mPaint); |
834 | 0 | } |
835 | | |
836 | | static Double |
837 | | DashPeriodLength(const StrokeOptions& aStrokeOptions) |
838 | 0 | { |
839 | 0 | Double length = 0; |
840 | 0 | for (size_t i = 0; i < aStrokeOptions.mDashLength; i++) { |
841 | 0 | length += aStrokeOptions.mDashPattern[i]; |
842 | 0 | } |
843 | 0 | if (aStrokeOptions.mDashLength & 1) { |
844 | 0 | // "If an odd number of values is provided, then the list of values is |
845 | 0 | // repeated to yield an even number of values." |
846 | 0 | // Double the length. |
847 | 0 | length += length; |
848 | 0 | } |
849 | 0 | return length; |
850 | 0 | } |
851 | | |
852 | | static inline Double |
853 | | RoundDownToMultiple(Double aValue, Double aFactor) |
854 | 0 | { |
855 | 0 | return floor(aValue / aFactor) * aFactor; |
856 | 0 | } |
857 | | |
858 | | static Rect |
859 | | UserSpaceStrokeClip(const IntRect &aDeviceClip, |
860 | | const Matrix &aTransform, |
861 | | const StrokeOptions &aStrokeOptions) |
862 | 0 | { |
863 | 0 | Matrix inverse = aTransform; |
864 | 0 | if (!inverse.Invert()) { |
865 | 0 | return Rect(); |
866 | 0 | } |
867 | 0 | Rect deviceClip(aDeviceClip); |
868 | 0 | deviceClip.Inflate(MaxStrokeExtents(aStrokeOptions, aTransform)); |
869 | 0 | return inverse.TransformBounds(deviceClip); |
870 | 0 | } |
871 | | |
872 | | static Rect |
873 | | ShrinkClippedStrokedRect(const Rect &aStrokedRect, const IntRect &aDeviceClip, |
874 | | const Matrix &aTransform, |
875 | | const StrokeOptions &aStrokeOptions) |
876 | 0 | { |
877 | 0 | Rect userSpaceStrokeClip = |
878 | 0 | UserSpaceStrokeClip(aDeviceClip, aTransform, aStrokeOptions); |
879 | 0 | RectDouble strokedRectDouble( |
880 | 0 | aStrokedRect.X(), aStrokedRect.Y(), aStrokedRect.Width(), aStrokedRect.Height()); |
881 | 0 | RectDouble intersection = |
882 | 0 | strokedRectDouble.Intersect(RectDouble(userSpaceStrokeClip.X(), |
883 | 0 | userSpaceStrokeClip.Y(), |
884 | 0 | userSpaceStrokeClip.Width(), |
885 | 0 | userSpaceStrokeClip.Height())); |
886 | 0 | Double dashPeriodLength = DashPeriodLength(aStrokeOptions); |
887 | 0 | if (intersection.IsEmpty() || dashPeriodLength == 0.0f) { |
888 | 0 | return Rect(intersection.X(), intersection.Y(), |
889 | 0 | intersection.Width(), intersection.Height()); |
890 | 0 | } |
891 | 0 | |
892 | 0 | // Reduce the rectangle side lengths in multiples of the dash period length |
893 | 0 | // so that the visible dashes stay in the same place. |
894 | 0 | MarginDouble insetBy = strokedRectDouble - intersection; |
895 | 0 | insetBy.top = RoundDownToMultiple(insetBy.top, dashPeriodLength); |
896 | 0 | insetBy.right = RoundDownToMultiple(insetBy.right, dashPeriodLength); |
897 | 0 | insetBy.bottom = RoundDownToMultiple(insetBy.bottom, dashPeriodLength); |
898 | 0 | insetBy.left = RoundDownToMultiple(insetBy.left, dashPeriodLength); |
899 | 0 |
|
900 | 0 | strokedRectDouble.Deflate(insetBy); |
901 | 0 | return Rect(strokedRectDouble.X(), |
902 | 0 | strokedRectDouble.Y(), |
903 | 0 | strokedRectDouble.Width(), |
904 | 0 | strokedRectDouble.Height()); |
905 | 0 | } |
906 | | |
907 | | void |
908 | | DrawTargetSkia::StrokeRect(const Rect &aRect, |
909 | | const Pattern &aPattern, |
910 | | const StrokeOptions &aStrokeOptions, |
911 | | const DrawOptions &aOptions) |
912 | 0 | { |
913 | 0 | // Stroking large rectangles with dashes is expensive with Skia (fixed |
914 | 0 | // overhead based on the number of dashes, regardless of whether the dashes |
915 | 0 | // are visible), so we try to reduce the size of the stroked rectangle as |
916 | 0 | // much as possible before passing it on to Skia. |
917 | 0 | Rect rect = aRect; |
918 | 0 | if (aStrokeOptions.mDashLength > 0 && !rect.IsEmpty()) { |
919 | 0 | IntRect deviceClip(IntPoint(0, 0), mSize); |
920 | 0 | SkIRect clipBounds; |
921 | 0 | if (mCanvas->getDeviceClipBounds(&clipBounds)) { |
922 | 0 | deviceClip = SkIRectToIntRect(clipBounds); |
923 | 0 | } |
924 | 0 | rect = ShrinkClippedStrokedRect(rect, deviceClip, mTransform, aStrokeOptions); |
925 | 0 | if (rect.IsEmpty()) { |
926 | 0 | return; |
927 | 0 | } |
928 | 0 | } |
929 | 0 | |
930 | 0 | MarkChanged(); |
931 | 0 | AutoPaintSetup paint(mCanvas, aOptions, aPattern); |
932 | 0 | if (!StrokeOptionsToPaint(paint.mPaint, aStrokeOptions)) { |
933 | 0 | return; |
934 | 0 | } |
935 | 0 | |
936 | 0 | mCanvas->drawRect(RectToSkRect(rect), paint.mPaint); |
937 | 0 | } |
938 | | |
939 | | void |
940 | | DrawTargetSkia::StrokeLine(const Point &aStart, |
941 | | const Point &aEnd, |
942 | | const Pattern &aPattern, |
943 | | const StrokeOptions &aStrokeOptions, |
944 | | const DrawOptions &aOptions) |
945 | 0 | { |
946 | 0 | MarkChanged(); |
947 | 0 | AutoPaintSetup paint(mCanvas, aOptions, aPattern); |
948 | 0 | if (!StrokeOptionsToPaint(paint.mPaint, aStrokeOptions)) { |
949 | 0 | return; |
950 | 0 | } |
951 | 0 | |
952 | 0 | mCanvas->drawLine(SkFloatToScalar(aStart.x), SkFloatToScalar(aStart.y), |
953 | 0 | SkFloatToScalar(aEnd.x), SkFloatToScalar(aEnd.y), |
954 | 0 | paint.mPaint); |
955 | 0 | } |
956 | | |
957 | | void |
958 | | DrawTargetSkia::Fill(const Path *aPath, |
959 | | const Pattern &aPattern, |
960 | | const DrawOptions &aOptions) |
961 | 0 | { |
962 | 0 | MarkChanged(); |
963 | 0 | if (!aPath || aPath->GetBackendType() != BackendType::SKIA) { |
964 | 0 | return; |
965 | 0 | } |
966 | 0 | |
967 | 0 | const PathSkia *skiaPath = static_cast<const PathSkia*>(aPath); |
968 | 0 |
|
969 | 0 | AutoPaintSetup paint(mCanvas, aOptions, aPattern); |
970 | 0 |
|
971 | 0 | if (!skiaPath->GetPath().isFinite()) { |
972 | 0 | return; |
973 | 0 | } |
974 | 0 | |
975 | 0 | mCanvas->drawPath(skiaPath->GetPath(), paint.mPaint); |
976 | 0 | } |
977 | | |
978 | | bool |
979 | | DrawTargetSkia::ShouldLCDRenderText(FontType aFontType, AntialiasMode aAntialiasMode) |
980 | 0 | { |
981 | 0 | // Only allow subpixel AA if explicitly permitted. |
982 | 0 | if (!GetPermitSubpixelAA()) { |
983 | 0 | return false; |
984 | 0 | } |
985 | 0 | |
986 | 0 | if (aAntialiasMode == AntialiasMode::DEFAULT) { |
987 | 0 | switch (aFontType) { |
988 | 0 | case FontType::MAC: |
989 | 0 | case FontType::GDI: |
990 | 0 | case FontType::DWRITE: |
991 | 0 | case FontType::FONTCONFIG: |
992 | 0 | return true; |
993 | 0 | case FontType::FREETYPE: |
994 | 0 | default: |
995 | 0 | // TODO: Figure out what to do for the other platforms. |
996 | 0 | return false; |
997 | 0 | } |
998 | 0 | } |
999 | 0 | return (aAntialiasMode == AntialiasMode::SUBPIXEL); |
1000 | 0 | } |
1001 | | |
1002 | | #ifdef MOZ_WIDGET_COCOA |
1003 | | static inline CGAffineTransform |
1004 | | GfxMatrixToCGAffineTransform(const Matrix &m) |
1005 | | { |
1006 | | CGAffineTransform t; |
1007 | | t.a = m._11; |
1008 | | t.b = m._12; |
1009 | | t.c = m._21; |
1010 | | t.d = m._22; |
1011 | | t.tx = m._31; |
1012 | | t.ty = m._32; |
1013 | | return t; |
1014 | | } |
1015 | | |
1016 | | /*** |
1017 | | * We have to do a lot of work to draw glyphs with CG because |
1018 | | * CG assumes that the origin of rects are in the bottom left |
1019 | | * while every other DrawTarget assumes the top left is the origin. |
1020 | | * This means we have to transform the CGContext to have rects |
1021 | | * actually be applied in top left fashion. We do this by: |
1022 | | * |
1023 | | * 1) Translating the context up by the height of the canvas |
1024 | | * 2) Flipping the context by the Y axis so it's upside down. |
1025 | | * |
1026 | | * These two transforms put the origin in the top left. |
1027 | | * Transforms are better understood thinking about them from right to left order (mathematically). |
1028 | | * |
1029 | | * Consider a point we want to draw at (0, 10) in normal cartesian planes with |
1030 | | * a box of (100, 100). in CG terms, this would be at (0, 10). |
1031 | | * Positive Y values point up. |
1032 | | * In our DrawTarget terms, positive Y values point down, so (0, 10) would be |
1033 | | * at (0, 90) in cartesian plane terms. That means our point at (0, 10) in DrawTarget |
1034 | | * terms should end up at (0, 90). How does this work with the current transforms? |
1035 | | * |
1036 | | * Going right to left with the transforms, a CGPoint of (0, 10) has cartesian coordinates |
1037 | | * of (0, 10). The first flip of the Y axis puts the point now at (0, -10); |
1038 | | * Next, we translate the context up by the size of the canvas (Positive Y values go up in CG |
1039 | | * coordinates but down in our draw target coordinates). Since our canvas size is (100, 100), |
1040 | | * the resulting coordinate becomes (0, 90), which is what we expect from our DrawTarget code. |
1041 | | * These two transforms put the CG context equal to what every other DrawTarget expects. |
1042 | | * |
1043 | | * Next, we need two more transforms for actual text. IF we left the transforms as is, |
1044 | | * the text would be drawn upside down, so we need another flip of the Y axis |
1045 | | * to draw the text right side up. However, with only the flip, the text would be drawn |
1046 | | * in the wrong place. Thus we also have to invert the Y position of the glyphs to get them |
1047 | | * in the right place. |
1048 | | * |
1049 | | * Thus we have the following transforms: |
1050 | | * 1) Translation of the context up |
1051 | | * 2) Flipping the context around the Y axis |
1052 | | * 3) Flipping the context around the Y axis |
1053 | | * 4) Inverting the Y position of each glyph |
1054 | | * |
1055 | | * We cannot cancel out (2) and (3) as we have to apply the clips and transforms |
1056 | | * of DrawTargetSkia between (2) and (3). |
1057 | | * |
1058 | | * Consider the example letter P, drawn at (0, 20) in CG coordinates in a (100, 100) rect. |
1059 | | * Again, going right to left of the transforms. We'd get: |
1060 | | * |
1061 | | * 1) The letter P drawn at (0, -20) due to the inversion of the Y axis |
1062 | | * 2) The letter P upside down (b) at (0, 20) due to the second flip |
1063 | | * 3) The letter P right side up at (0, -20) due to the first flip |
1064 | | * 4) The letter P right side up at (0, 80) due to the translation |
1065 | | * |
1066 | | * tl;dr - CGRects assume origin is bottom left, DrawTarget rects assume top left. |
1067 | | */ |
1068 | | static bool |
1069 | | SetupCGContext(DrawTargetSkia* aDT, |
1070 | | CGContextRef aCGContext, |
1071 | | SkCanvas* aCanvas, |
1072 | | const IntPoint& aOrigin, |
1073 | | const IntSize& aSize, |
1074 | | bool aClipped) |
1075 | | { |
1076 | | // DrawTarget expects the origin to be at the top left, but CG |
1077 | | // expects it to be at the bottom left. Transform to set the origin to |
1078 | | // the top left. Have to set this before we do anything else. |
1079 | | // This is transform (1) up top |
1080 | | CGContextTranslateCTM(aCGContext, -aOrigin.x, aOrigin.y + aSize.height); |
1081 | | |
1082 | | // Transform (2) from the comments. |
1083 | | CGContextScaleCTM(aCGContext, 1, -1); |
1084 | | |
1085 | | // Want to apply clips BEFORE the transform since the transform |
1086 | | // will apply to the clips we apply. |
1087 | | if (aClipped) { |
1088 | | SkRegion clipRegion; |
1089 | | aCanvas->temporary_internal_getRgnClip(&clipRegion); |
1090 | | Vector<CGRect, 8> rects; |
1091 | | for (SkRegion::Iterator it(clipRegion); !it.done(); it.next()) { |
1092 | | const SkIRect& rect = it.rect(); |
1093 | | if (!rects.append(CGRectMake(rect.x(), rect.y(), rect.width(), rect.height()))) { |
1094 | | break; |
1095 | | } |
1096 | | } |
1097 | | if (rects.length()) { |
1098 | | CGContextClipToRects(aCGContext, rects.begin(), rects.length()); |
1099 | | } |
1100 | | } |
1101 | | |
1102 | | CGContextConcatCTM(aCGContext, GfxMatrixToCGAffineTransform(aDT->GetTransform())); |
1103 | | return true; |
1104 | | } |
1105 | | |
1106 | | static bool |
1107 | | SetupCGGlyphs(CGContextRef aCGContext, |
1108 | | const GlyphBuffer& aBuffer, |
1109 | | Vector<CGGlyph,32>& aGlyphs, |
1110 | | Vector<CGPoint,32>& aPositions) |
1111 | | { |
1112 | | // Flip again so we draw text in right side up. Transform (3) from the top |
1113 | | CGContextScaleCTM(aCGContext, 1, -1); |
1114 | | |
1115 | | if (!aGlyphs.resizeUninitialized(aBuffer.mNumGlyphs) || |
1116 | | !aPositions.resizeUninitialized(aBuffer.mNumGlyphs)) { |
1117 | | gfxDevCrash(LogReason::GlyphAllocFailedCG) << "glyphs/positions allocation failed"; |
1118 | | return false; |
1119 | | } |
1120 | | |
1121 | | for (unsigned int i = 0; i < aBuffer.mNumGlyphs; i++) { |
1122 | | aGlyphs[i] = aBuffer.mGlyphs[i].mIndex; |
1123 | | |
1124 | | // Flip the y coordinates so that text ends up in the right spot after the (3) flip |
1125 | | // Inversion from (4) in the comments. |
1126 | | aPositions[i] = CGPointMake(aBuffer.mGlyphs[i].mPosition.x, |
1127 | | -aBuffer.mGlyphs[i].mPosition.y); |
1128 | | } |
1129 | | |
1130 | | return true; |
1131 | | } |
1132 | | // End long comment about transforms. SetupCGContext and SetupCGGlyphs should stay |
1133 | | // next to each other. |
1134 | | |
1135 | | // The context returned from this method will have the origin |
1136 | | // in the top left and will have applied all the neccessary clips |
1137 | | // and transforms to the CGContext. See the comment above |
1138 | | // SetupCGContext. |
1139 | | CGContextRef |
1140 | | DrawTargetSkia::BorrowCGContext(const DrawOptions &aOptions) |
1141 | | { |
1142 | | // Since we can't replay Skia clips, we have to use a layer if we have a complex clip. |
1143 | | // After saving a layer, the SkCanvas queries for needing a layer change so save if we |
1144 | | // pushed a layer. |
1145 | | mNeedLayer = !mCanvas->isClipEmpty() && !mCanvas->isClipRect(); |
1146 | | if (mNeedLayer) { |
1147 | | SkPaint paint; |
1148 | | paint.setBlendMode(SkBlendMode::kSrc); |
1149 | | SkCanvas::SaveLayerRec rec(nullptr, &paint, SkCanvas::kInitWithPrevious_SaveLayerFlag); |
1150 | | mCanvas->saveLayer(rec); |
1151 | | } |
1152 | | |
1153 | | uint8_t* data = nullptr; |
1154 | | int32_t stride; |
1155 | | SurfaceFormat format; |
1156 | | IntSize size; |
1157 | | IntPoint origin; |
1158 | | if (!LockBits(&data, &size, &stride, &format, &origin)) { |
1159 | | NS_WARNING("Could not lock skia bits to wrap CG around"); |
1160 | | return nullptr; |
1161 | | } |
1162 | | |
1163 | | if (!mNeedLayer && (data == mCanvasData) && mCG && (mCGSize == size)) { |
1164 | | // If our canvas data still points to the same data, |
1165 | | // we can reuse the CG Context |
1166 | | CGContextSetAlpha(mCG, aOptions.mAlpha); |
1167 | | CGContextSetShouldAntialias(mCG, aOptions.mAntialiasMode != AntialiasMode::NONE); |
1168 | | CGContextSaveGState(mCG); |
1169 | | SetupCGContext(this, mCG, mCanvas, origin, size, true); |
1170 | | return mCG; |
1171 | | } |
1172 | | |
1173 | | if (!mColorSpace) { |
1174 | | mColorSpace = (format == SurfaceFormat::A8) ? |
1175 | | CGColorSpaceCreateDeviceGray() : CGColorSpaceCreateDeviceRGB(); |
1176 | | } |
1177 | | |
1178 | | if (mCG) { |
1179 | | // Release the old CG context since it's no longer valid. |
1180 | | CGContextRelease(mCG); |
1181 | | } |
1182 | | |
1183 | | mCanvasData = data; |
1184 | | mCGSize = size; |
1185 | | |
1186 | | uint32_t bitmapInfo = (format == SurfaceFormat::A8) ? |
1187 | | kCGImageAlphaOnly : |
1188 | | kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host; |
1189 | | |
1190 | | mCG = CGBitmapContextCreateWithData(mCanvasData, |
1191 | | mCGSize.width, |
1192 | | mCGSize.height, |
1193 | | 8, /* bits per component */ |
1194 | | stride, |
1195 | | mColorSpace, |
1196 | | bitmapInfo, |
1197 | | NULL, /* Callback when released */ |
1198 | | NULL); |
1199 | | if (!mCG) { |
1200 | | if (mNeedLayer) { |
1201 | | mCanvas->restore(); |
1202 | | } |
1203 | | ReleaseBits(mCanvasData); |
1204 | | NS_WARNING("Could not create bitmap around skia data\n"); |
1205 | | return nullptr; |
1206 | | } |
1207 | | |
1208 | | CGContextSetAlpha(mCG, aOptions.mAlpha); |
1209 | | CGContextSetShouldAntialias(mCG, aOptions.mAntialiasMode != AntialiasMode::NONE); |
1210 | | CGContextSetShouldSmoothFonts(mCG, true); |
1211 | | CGContextSetTextDrawingMode(mCG, kCGTextFill); |
1212 | | CGContextSaveGState(mCG); |
1213 | | SetupCGContext(this, mCG, mCanvas, origin, size, !mNeedLayer); |
1214 | | return mCG; |
1215 | | } |
1216 | | |
1217 | | void |
1218 | | DrawTargetSkia::ReturnCGContext(CGContextRef aCGContext) |
1219 | | { |
1220 | | MOZ_ASSERT(aCGContext == mCG); |
1221 | | ReleaseBits(mCanvasData); |
1222 | | CGContextRestoreGState(aCGContext); |
1223 | | |
1224 | | if (mNeedLayer) { |
1225 | | // A layer was used for clipping and is about to be popped by the restore. |
1226 | | // Make sure the CG context referencing it is released first so the popped |
1227 | | // layer doesn't accidentally get used. |
1228 | | if (mCG) { |
1229 | | CGContextRelease(mCG); |
1230 | | mCG = nullptr; |
1231 | | } |
1232 | | mCanvas->restore(); |
1233 | | } |
1234 | | } |
1235 | | |
1236 | | CGContextRef |
1237 | | BorrowedCGContext::BorrowCGContextFromDrawTarget(DrawTarget *aDT) |
1238 | | { |
1239 | | DrawTargetSkia* skiaDT = static_cast<DrawTargetSkia*>(aDT); |
1240 | | return skiaDT->BorrowCGContext(DrawOptions()); |
1241 | | } |
1242 | | |
1243 | | void |
1244 | | BorrowedCGContext::ReturnCGContextToDrawTarget(DrawTarget *aDT, CGContextRef cg) |
1245 | | { |
1246 | | DrawTargetSkia* skiaDT = static_cast<DrawTargetSkia*>(aDT); |
1247 | | skiaDT->ReturnCGContext(cg); |
1248 | | return; |
1249 | | } |
1250 | | |
1251 | | static void |
1252 | | SetFontColor(CGContextRef aCGContext, CGColorSpaceRef aColorSpace, const Pattern& aPattern) |
1253 | | { |
1254 | | const Color& color = static_cast<const ColorPattern&>(aPattern).mColor; |
1255 | | CGColorRef textColor = ColorToCGColor(aColorSpace, color); |
1256 | | CGContextSetFillColorWithColor(aCGContext, textColor); |
1257 | | CGColorRelease(textColor); |
1258 | | } |
1259 | | |
1260 | | /*** |
1261 | | * We need this to support subpixel AA text on OS X in two cases: |
1262 | | * text in DrawTargets that are not opaque and text over vibrant backgrounds. |
1263 | | * Skia normally doesn't support subpixel AA text on transparent backgrounds. |
1264 | | * To get around this, we have to wrap the Skia bytes with a CGContext and ask |
1265 | | * CG to draw the text. |
1266 | | * In vibrancy cases, we have to use a private API, |
1267 | | * CGContextSetFontSmoothingBackgroundColor, which sets the expected |
1268 | | * background color the text will draw onto so that CG can render the text |
1269 | | * properly. After that, we have to go back and fixup the pixels |
1270 | | * such that their alpha values are correct. |
1271 | | */ |
1272 | | bool |
1273 | | DrawTargetSkia::FillGlyphsWithCG(ScaledFont *aFont, |
1274 | | const GlyphBuffer &aBuffer, |
1275 | | const Pattern &aPattern, |
1276 | | const DrawOptions &aOptions) |
1277 | | { |
1278 | | MOZ_ASSERT(aFont->GetType() == FontType::MAC); |
1279 | | MOZ_ASSERT(aPattern.GetType() == PatternType::COLOR); |
1280 | | |
1281 | | CGContextRef cgContext = BorrowCGContext(aOptions); |
1282 | | if (!cgContext) { |
1283 | | return false; |
1284 | | } |
1285 | | |
1286 | | Vector<CGGlyph,32> glyphs; |
1287 | | Vector<CGPoint,32> positions; |
1288 | | if (!SetupCGGlyphs(cgContext, aBuffer, glyphs, positions)) { |
1289 | | ReturnCGContext(cgContext); |
1290 | | return false; |
1291 | | } |
1292 | | |
1293 | | ScaledFontMac* macFont = static_cast<ScaledFontMac*>(aFont); |
1294 | | SetFontSmoothingBackgroundColor(cgContext, mColorSpace, |
1295 | | macFont->FontSmoothingBackgroundColor()); |
1296 | | SetFontColor(cgContext, mColorSpace, aPattern); |
1297 | | |
1298 | | if (ScaledFontMac::CTFontDrawGlyphsPtr != nullptr) { |
1299 | | ScaledFontMac::CTFontDrawGlyphsPtr(macFont->mCTFont, glyphs.begin(), |
1300 | | positions.begin(), |
1301 | | aBuffer.mNumGlyphs, cgContext); |
1302 | | } else { |
1303 | | CGContextSetFont(cgContext, macFont->mFont); |
1304 | | CGContextSetFontSize(cgContext, macFont->mSize); |
1305 | | CGContextShowGlyphsAtPositions(cgContext, glyphs.begin(), positions.begin(), |
1306 | | aBuffer.mNumGlyphs); |
1307 | | } |
1308 | | |
1309 | | // Calculate the area of the text we just drew |
1310 | | auto *bboxes = new CGRect[aBuffer.mNumGlyphs]; |
1311 | | CTFontGetBoundingRectsForGlyphs(macFont->mCTFont, kCTFontDefaultOrientation, |
1312 | | glyphs.begin(), bboxes, aBuffer.mNumGlyphs); |
1313 | | CGRect extents = ComputeGlyphsExtents(bboxes, positions.begin(), aBuffer.mNumGlyphs, 1.0f); |
1314 | | delete[] bboxes; |
1315 | | |
1316 | | CGAffineTransform cgTransform = CGContextGetCTM(cgContext); |
1317 | | extents = CGRectApplyAffineTransform(extents, cgTransform); |
1318 | | |
1319 | | // Have to round it out to ensure we fully cover all pixels |
1320 | | Rect rect(extents.origin.x, extents.origin.y, extents.size.width, extents.size.height); |
1321 | | rect.RoundOut(); |
1322 | | extents = CGRectMake(rect.x, rect.y, rect.width, rect.height); |
1323 | | |
1324 | | EnsureValidPremultipliedData(cgContext, extents); |
1325 | | |
1326 | | ReturnCGContext(cgContext); |
1327 | | return true; |
1328 | | } |
1329 | | |
1330 | | static bool |
1331 | | HasFontSmoothingBackgroundColor(ScaledFont* aFont) |
1332 | | { |
1333 | | // This should generally only be true if we have a popup context menu |
1334 | | if (aFont && aFont->GetType() == FontType::MAC) { |
1335 | | Color fontSmoothingBackgroundColor = |
1336 | | static_cast<ScaledFontMac*>(aFont)->FontSmoothingBackgroundColor(); |
1337 | | return fontSmoothingBackgroundColor.a > 0; |
1338 | | } |
1339 | | |
1340 | | return false; |
1341 | | } |
1342 | | |
1343 | | static bool |
1344 | | ShouldUseCGToFillGlyphs(ScaledFont* aFont, const Pattern& aPattern) |
1345 | | { |
1346 | | return HasFontSmoothingBackgroundColor(aFont) && |
1347 | | aPattern.GetType() == PatternType::COLOR; |
1348 | | } |
1349 | | |
1350 | | #endif |
1351 | | |
1352 | | static bool |
1353 | | CanDrawFont(ScaledFont* aFont) |
1354 | | { |
1355 | | switch (aFont->GetType()) { |
1356 | | case FontType::FREETYPE: |
1357 | | case FontType::FONTCONFIG: |
1358 | | case FontType::MAC: |
1359 | | case FontType::GDI: |
1360 | | case FontType::DWRITE: |
1361 | | return true; |
1362 | | default: |
1363 | | return false; |
1364 | | } |
1365 | | } |
1366 | | |
1367 | | void |
1368 | | DrawTargetSkia::DrawGlyphs(ScaledFont* aFont, |
1369 | | const GlyphBuffer& aBuffer, |
1370 | | const Pattern& aPattern, |
1371 | | const StrokeOptions* aStrokeOptions, |
1372 | | const DrawOptions& aOptions) |
1373 | 0 | { |
1374 | 0 | if (!CanDrawFont(aFont)) { |
1375 | 0 | return; |
1376 | 0 | } |
1377 | 0 | |
1378 | 0 | MarkChanged(); |
1379 | 0 |
|
1380 | | #ifdef MOZ_WIDGET_COCOA |
1381 | | if (!aStrokeOptions && |
1382 | | ShouldUseCGToFillGlyphs(aFont, aPattern)) { |
1383 | | if (FillGlyphsWithCG(aFont, aBuffer, aPattern, aOptions)) { |
1384 | | return; |
1385 | | } |
1386 | | } |
1387 | | #endif |
1388 | |
|
1389 | 0 | ScaledFontBase* skiaFont = static_cast<ScaledFontBase*>(aFont); |
1390 | 0 | SkTypeface* typeface = skiaFont->GetSkTypeface(); |
1391 | 0 | if (!typeface) { |
1392 | 0 | return; |
1393 | 0 | } |
1394 | 0 | |
1395 | 0 | AutoPaintSetup paint(mCanvas, aOptions, aPattern); |
1396 | 0 | if (aStrokeOptions && |
1397 | 0 | !StrokeOptionsToPaint(paint.mPaint, *aStrokeOptions)) { |
1398 | 0 | return; |
1399 | 0 | } |
1400 | 0 | |
1401 | 0 | AntialiasMode aaMode = aFont->GetDefaultAAMode(); |
1402 | 0 | if (aOptions.mAntialiasMode != AntialiasMode::DEFAULT) { |
1403 | 0 | aaMode = aOptions.mAntialiasMode; |
1404 | 0 | } |
1405 | 0 | bool aaEnabled = aaMode != AntialiasMode::NONE; |
1406 | 0 |
|
1407 | 0 | paint.mPaint.setAntiAlias(aaEnabled); |
1408 | 0 | paint.mPaint.setTypeface(sk_ref_sp(typeface)); |
1409 | 0 | paint.mPaint.setTextSize(SkFloatToScalar(skiaFont->mSize)); |
1410 | 0 | paint.mPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); |
1411 | 0 |
|
1412 | 0 | bool shouldLCDRenderText = ShouldLCDRenderText(aFont->GetType(), aaMode); |
1413 | 0 | paint.mPaint.setLCDRenderText(shouldLCDRenderText); |
1414 | 0 |
|
1415 | 0 | bool useSubpixelText = true; |
1416 | 0 |
|
1417 | 0 | switch (aFont->GetType()) { |
1418 | 0 | case FontType::FREETYPE: |
1419 | 0 | case FontType::FONTCONFIG: |
1420 | 0 | // SkFontHost_cairo does not support subpixel text positioning, |
1421 | 0 | // so only enable it for other font hosts. |
1422 | 0 | useSubpixelText = false; |
1423 | 0 | break; |
1424 | 0 | case FontType::MAC: |
1425 | 0 | if (aaMode == AntialiasMode::GRAY) { |
1426 | 0 | // Normally, Skia enables LCD FontSmoothing which creates thicker fonts |
1427 | 0 | // and also enables subpixel AA. CoreGraphics without font smoothing |
1428 | 0 | // explicitly creates thinner fonts and grayscale AA. |
1429 | 0 | // CoreGraphics doesn't support a configuration that produces thicker |
1430 | 0 | // fonts with grayscale AA as LCD Font Smoothing enables or disables both. |
1431 | 0 | // However, Skia supports it by enabling font smoothing (producing subpixel AA) |
1432 | 0 | // and converts it to grayscale AA. Since Skia doesn't support subpixel AA on |
1433 | 0 | // transparent backgrounds, we still want font smoothing for the thicker fonts, |
1434 | 0 | // even if it is grayscale AA. |
1435 | 0 | // |
1436 | 0 | // With explicit Grayscale AA (from -moz-osx-font-smoothing:grayscale), |
1437 | 0 | // we want to have grayscale AA with no smoothing at all. This means |
1438 | 0 | // disabling the LCD font smoothing behaviour. |
1439 | 0 | // To accomplish this we have to explicitly disable hinting, |
1440 | 0 | // and disable LCDRenderText. |
1441 | 0 | paint.mPaint.setHinting(SkPaint::kNo_Hinting); |
1442 | 0 | } |
1443 | 0 | break; |
1444 | 0 | case FontType::GDI: |
1445 | 0 | { |
1446 | 0 | if (!shouldLCDRenderText && aaEnabled) { |
1447 | 0 | // If we have non LCD GDI text, render the fonts as cleartype and convert them |
1448 | 0 | // to grayscale. This seems to be what Chrome and IE are doing on Windows 7. |
1449 | 0 | // This also applies if cleartype is disabled system wide. |
1450 | 0 | paint.mPaint.setFlags(paint.mPaint.getFlags() | SkPaint::kGenA8FromLCD_Flag); |
1451 | 0 | } |
1452 | 0 | break; |
1453 | 0 | } |
1454 | | #ifdef XP_WIN |
1455 | | case FontType::DWRITE: |
1456 | | { |
1457 | | ScaledFontDWrite* dwriteFont = static_cast<ScaledFontDWrite*>(aFont); |
1458 | | paint.mPaint.setEmbeddedBitmapText(dwriteFont->UseEmbeddedBitmaps()); |
1459 | | |
1460 | | if (dwriteFont->ForceGDIMode()) { |
1461 | | paint.mPaint.setEmbeddedBitmapText(true); |
1462 | | useSubpixelText = false; |
1463 | | } |
1464 | | break; |
1465 | | } |
1466 | | #endif |
1467 | 0 | default: |
1468 | 0 | break; |
1469 | 0 | } |
1470 | 0 | |
1471 | 0 | paint.mPaint.setSubpixelText(useSubpixelText); |
1472 | 0 |
|
1473 | 0 | const uint32_t heapSize = 64; |
1474 | 0 | uint16_t indicesOnStack[heapSize]; |
1475 | 0 | SkPoint offsetsOnStack[heapSize]; |
1476 | 0 | std::vector<uint16_t> indicesOnHeap; |
1477 | 0 | std::vector<SkPoint> offsetsOnHeap; |
1478 | 0 | uint16_t* indices = indicesOnStack; |
1479 | 0 | SkPoint* offsets = offsetsOnStack; |
1480 | 0 | if (aBuffer.mNumGlyphs > heapSize) { |
1481 | 0 | // Heap allocation/ deallocation is slow, use it only if we need a |
1482 | 0 | // bigger(>heapSize) buffer. |
1483 | 0 | indicesOnHeap.resize(aBuffer.mNumGlyphs); |
1484 | 0 | offsetsOnHeap.resize(aBuffer.mNumGlyphs); |
1485 | 0 | indices = (uint16_t*)&indicesOnHeap.front(); |
1486 | 0 | offsets = (SkPoint*)&offsetsOnHeap.front(); |
1487 | 0 | } |
1488 | 0 |
|
1489 | 0 | for (unsigned int i = 0; i < aBuffer.mNumGlyphs; i++) { |
1490 | 0 | indices[i] = aBuffer.mGlyphs[i].mIndex; |
1491 | 0 | offsets[i].fX = SkFloatToScalar(aBuffer.mGlyphs[i].mPosition.x); |
1492 | 0 | offsets[i].fY = SkFloatToScalar(aBuffer.mGlyphs[i].mPosition.y); |
1493 | 0 | } |
1494 | 0 |
|
1495 | 0 | mCanvas->drawPosText(indices, aBuffer.mNumGlyphs*2, offsets, paint.mPaint); |
1496 | 0 | } |
1497 | | |
1498 | | void |
1499 | | DrawTargetSkia::FillGlyphs(ScaledFont* aFont, |
1500 | | const GlyphBuffer& aBuffer, |
1501 | | const Pattern& aPattern, |
1502 | | const DrawOptions& aOptions) |
1503 | 0 | { |
1504 | 0 | DrawGlyphs(aFont, aBuffer, aPattern, nullptr, aOptions); |
1505 | 0 | } |
1506 | | |
1507 | | void |
1508 | | DrawTargetSkia::StrokeGlyphs(ScaledFont* aFont, |
1509 | | const GlyphBuffer& aBuffer, |
1510 | | const Pattern& aPattern, |
1511 | | const StrokeOptions& aStrokeOptions, |
1512 | | const DrawOptions& aOptions) |
1513 | 0 | { |
1514 | 0 | DrawGlyphs(aFont, aBuffer, aPattern, &aStrokeOptions, aOptions); |
1515 | 0 | } |
1516 | | |
1517 | | void |
1518 | | DrawTargetSkia::Mask(const Pattern &aSource, |
1519 | | const Pattern &aMask, |
1520 | | const DrawOptions &aOptions) |
1521 | 0 | { |
1522 | 0 | SkIRect maskBounds; |
1523 | 0 | if (!mCanvas->getDeviceClipBounds(&maskBounds)) { |
1524 | 0 | return; |
1525 | 0 | } |
1526 | 0 | SkPoint maskOrigin; |
1527 | 0 | maskOrigin.iset(maskBounds.fLeft, maskBounds.fTop); |
1528 | 0 |
|
1529 | 0 | SkMatrix maskMatrix = mCanvas->getTotalMatrix(); |
1530 | 0 | maskMatrix.postTranslate(-maskOrigin.fX, -maskOrigin.fY); |
1531 | 0 |
|
1532 | 0 | MarkChanged(); |
1533 | 0 | AutoPaintSetup paint(mCanvas, aOptions, aSource, nullptr, &maskMatrix); |
1534 | 0 |
|
1535 | 0 | SkPaint maskPaint; |
1536 | 0 | SetPaintPattern(maskPaint, aMask); |
1537 | 0 |
|
1538 | 0 | SkBitmap maskBitmap; |
1539 | 0 | if (!maskBitmap.tryAllocPixelsFlags( |
1540 | 0 | SkImageInfo::MakeA8(maskBounds.width(), maskBounds.height()), |
1541 | 0 | SkBitmap::kZeroPixels_AllocFlag)) { |
1542 | 0 | return; |
1543 | 0 | } |
1544 | 0 | |
1545 | 0 | SkCanvas maskCanvas(maskBitmap); |
1546 | 0 | maskCanvas.setMatrix(maskMatrix); |
1547 | 0 | maskCanvas.drawPaint(maskPaint); |
1548 | 0 |
|
1549 | 0 | mCanvas->save(); |
1550 | 0 | mCanvas->resetMatrix(); |
1551 | 0 |
|
1552 | 0 | mCanvas->drawBitmap(maskBitmap, maskOrigin.fX, maskOrigin.fY, &paint.mPaint); |
1553 | 0 |
|
1554 | 0 | mCanvas->restore(); |
1555 | 0 | } |
1556 | | |
1557 | | void |
1558 | | DrawTargetSkia::MaskSurface(const Pattern &aSource, |
1559 | | SourceSurface *aMask, |
1560 | | Point aOffset, |
1561 | | const DrawOptions &aOptions) |
1562 | 0 | { |
1563 | 0 | MarkChanged(); |
1564 | 0 |
|
1565 | 0 | SkMatrix invOffset = SkMatrix::MakeTrans(SkFloatToScalar(-aOffset.x), SkFloatToScalar(-aOffset.y)); |
1566 | 0 | AutoPaintSetup paint(mCanvas, aOptions, aSource, nullptr, &invOffset); |
1567 | 0 |
|
1568 | 0 | sk_sp<SkImage> alphaMask = ExtractAlphaForSurface(aMask); |
1569 | 0 | if (!alphaMask) { |
1570 | 0 | gfxDebug() << *this << ": MaskSurface() failed to extract alpha for mask"; |
1571 | 0 | return; |
1572 | 0 | } |
1573 | 0 |
|
1574 | 0 | mCanvas->drawImage(alphaMask, aOffset.x, aOffset.y, &paint.mPaint); |
1575 | 0 | } |
1576 | | |
1577 | | bool |
1578 | | DrawTarget::Draw3DTransformedSurface(SourceSurface* aSurface, const Matrix4x4& aMatrix) |
1579 | 0 | { |
1580 | 0 | // Composite the 3D transform with the DT's transform. |
1581 | 0 | Matrix4x4 fullMat = aMatrix * Matrix4x4::From2D(mTransform); |
1582 | 0 | if (fullMat.IsSingular()) { |
1583 | 0 | return false; |
1584 | 0 | } |
1585 | 0 | // Transform the surface bounds and clip to this DT. |
1586 | 0 | IntRect xformBounds = |
1587 | 0 | RoundedOut( |
1588 | 0 | fullMat.TransformAndClipBounds(Rect(Point(0, 0), Size(aSurface->GetSize())), |
1589 | 0 | Rect(Point(0, 0), Size(GetSize())))); |
1590 | 0 | if (xformBounds.IsEmpty()) { |
1591 | 0 | return true; |
1592 | 0 | } |
1593 | 0 | // Offset the matrix by the transformed origin. |
1594 | 0 | fullMat.PostTranslate(-xformBounds.X(), -xformBounds.Y(), 0); |
1595 | 0 |
|
1596 | 0 | // Read in the source data. |
1597 | 0 | sk_sp<SkImage> srcImage = GetSkImageForSurface(aSurface); |
1598 | 0 | if (!srcImage) { |
1599 | 0 | return true; |
1600 | 0 | } |
1601 | 0 | |
1602 | 0 | // Set up an intermediate destination surface only the size of the transformed bounds. |
1603 | 0 | // Try to pass through the source's format unmodified in both the BGRA and ARGB cases. |
1604 | 0 | RefPtr<DataSourceSurface> dstSurf = |
1605 | 0 | Factory::CreateDataSourceSurface(xformBounds.Size(), |
1606 | 0 | !srcImage->isOpaque() ? |
1607 | 0 | aSurface->GetFormat() : SurfaceFormat::A8R8G8B8_UINT32, |
1608 | 0 | true); |
1609 | 0 | if (!dstSurf) { |
1610 | 0 | return false; |
1611 | 0 | } |
1612 | 0 | |
1613 | 0 | DataSourceSurface::ScopedMap map(dstSurf, DataSourceSurface::READ_WRITE); |
1614 | 0 | std::unique_ptr<SkCanvas> dstCanvas( |
1615 | 0 | SkCanvas::MakeRasterDirect( |
1616 | 0 | SkImageInfo::Make(xformBounds.Width(), xformBounds.Height(), |
1617 | 0 | GfxFormatToSkiaColorType(dstSurf->GetFormat()), |
1618 | 0 | kPremul_SkAlphaType), |
1619 | 0 | map.GetData(), map.GetStride())); |
1620 | 0 | if (!dstCanvas) { |
1621 | 0 | return false; |
1622 | 0 | } |
1623 | 0 | |
1624 | 0 | // Do the transform. |
1625 | 0 | SkPaint paint; |
1626 | 0 | paint.setAntiAlias(true); |
1627 | 0 | paint.setFilterQuality(kLow_SkFilterQuality); |
1628 | 0 | paint.setBlendMode(SkBlendMode::kSrc); |
1629 | 0 |
|
1630 | 0 | SkMatrix xform; |
1631 | 0 | GfxMatrixToSkiaMatrix(fullMat, xform); |
1632 | 0 | dstCanvas->setMatrix(xform); |
1633 | 0 |
|
1634 | 0 | dstCanvas->drawImage(srcImage, 0, 0, &paint); |
1635 | 0 | dstCanvas->flush(); |
1636 | 0 |
|
1637 | 0 | // Temporarily reset the DT's transform, since it has already been composed above. |
1638 | 0 | Matrix origTransform = mTransform; |
1639 | 0 | SetTransform(Matrix()); |
1640 | 0 |
|
1641 | 0 | // Draw the transformed surface within the transformed bounds. |
1642 | 0 | DrawSurface(dstSurf, Rect(xformBounds), Rect(Point(0, 0), Size(xformBounds.Size()))); |
1643 | 0 |
|
1644 | 0 | SetTransform(origTransform); |
1645 | 0 |
|
1646 | 0 | return true; |
1647 | 0 | } |
1648 | | |
1649 | | bool |
1650 | | DrawTargetSkia::Draw3DTransformedSurface(SourceSurface* aSurface, const Matrix4x4& aMatrix) |
1651 | 0 | { |
1652 | 0 | if (aMatrix.IsSingular()) { |
1653 | 0 | return false; |
1654 | 0 | } |
1655 | 0 | |
1656 | 0 | MarkChanged(); |
1657 | 0 |
|
1658 | 0 | sk_sp<SkImage> image = GetSkImageForSurface(aSurface); |
1659 | 0 | if (!image) { |
1660 | 0 | return true; |
1661 | 0 | } |
1662 | 0 | |
1663 | 0 | mCanvas->save(); |
1664 | 0 |
|
1665 | 0 | SkPaint paint; |
1666 | 0 | paint.setAntiAlias(true); |
1667 | 0 | paint.setFilterQuality(kLow_SkFilterQuality); |
1668 | 0 |
|
1669 | 0 | SkMatrix xform; |
1670 | 0 | GfxMatrixToSkiaMatrix(aMatrix, xform); |
1671 | 0 | mCanvas->concat(xform); |
1672 | 0 |
|
1673 | 0 | mCanvas->drawImage(image, 0, 0, &paint); |
1674 | 0 |
|
1675 | 0 | mCanvas->restore(); |
1676 | 0 |
|
1677 | 0 | return true; |
1678 | 0 | } |
1679 | | |
1680 | | already_AddRefed<SourceSurface> |
1681 | | DrawTargetSkia::CreateSourceSurfaceFromData(unsigned char *aData, |
1682 | | const IntSize &aSize, |
1683 | | int32_t aStride, |
1684 | | SurfaceFormat aFormat) const |
1685 | 0 | { |
1686 | 0 | RefPtr<SourceSurfaceSkia> newSurf = new SourceSurfaceSkia(); |
1687 | 0 |
|
1688 | 0 | if (!newSurf->InitFromData(aData, aSize, aStride, aFormat)) { |
1689 | 0 | gfxDebug() << *this << ": Failure to create source surface from data. Size: " << aSize; |
1690 | 0 | return nullptr; |
1691 | 0 | } |
1692 | 0 |
|
1693 | 0 | return newSurf.forget(); |
1694 | 0 | } |
1695 | | |
1696 | | already_AddRefed<DrawTarget> |
1697 | | DrawTargetSkia::CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const |
1698 | 0 | { |
1699 | 0 | RefPtr<DrawTargetSkia> target = new DrawTargetSkia(); |
1700 | 0 | #ifdef USE_SKIA_GPU |
1701 | 0 | if (UsingSkiaGPU()) { |
1702 | 0 | // Try to create a GPU draw target first if we're currently using the GPU. |
1703 | 0 | // Mark the DT as cached so that shadow DTs, extracted subrects, and similar can be reused. |
1704 | 0 | if (target->InitWithGrContext(mGrContext.get(), aSize, aFormat, true)) { |
1705 | 0 | return target.forget(); |
1706 | 0 | } |
1707 | 0 | // Otherwise, just fall back to a software draw target. |
1708 | 0 | } |
1709 | 0 | #endif |
1710 | 0 | |
1711 | | #ifdef DEBUG |
1712 | | if (!IsBackedByPixels(mCanvas)) { |
1713 | | // If our canvas is backed by vector storage such as PDF then we want to |
1714 | | // create a new DrawTarget with similar storage to avoid losing fidelity |
1715 | | // (fidelity will be lost if the returned DT is Snapshot()'ed and drawn |
1716 | | // back onto us since a raster will be drawn instead of vector commands). |
1717 | | NS_WARNING("Not backed by pixels - we need to handle PDF backed SkCanvas"); |
1718 | | } |
1719 | | #endif |
1720 | | |
1721 | 0 | if (!target->Init(aSize, aFormat)) { |
1722 | 0 | return nullptr; |
1723 | 0 | } |
1724 | 0 | return target.forget(); |
1725 | 0 | } |
1726 | | |
1727 | | bool |
1728 | | DrawTargetSkia::UsingSkiaGPU() const |
1729 | 0 | { |
1730 | 0 | #ifdef USE_SKIA_GPU |
1731 | 0 | return !!mGrContext; |
1732 | | #else |
1733 | | return false; |
1734 | | #endif |
1735 | | } |
1736 | | |
1737 | | #ifdef USE_SKIA_GPU |
1738 | | already_AddRefed<SourceSurface> |
1739 | | DrawTargetSkia::OptimizeGPUSourceSurface(SourceSurface *aSurface) const |
1740 | 0 | { |
1741 | 0 | // Check if the underlying SkImage already has an associated GrTexture. |
1742 | 0 | sk_sp<SkImage> image = GetSkImageForSurface(aSurface); |
1743 | 0 | if (!image || image->isTextureBacked()) { |
1744 | 0 | RefPtr<SourceSurface> surface(aSurface); |
1745 | 0 | return surface.forget(); |
1746 | 0 | } |
1747 | 0 | |
1748 | 0 | // Upload the SkImage to a GrTexture otherwise. |
1749 | 0 | sk_sp<SkImage> texture = image->makeTextureImage(mGrContext.get(), nullptr); |
1750 | 0 | if (texture) { |
1751 | 0 | // Create a new SourceSurfaceSkia whose SkImage contains the GrTexture. |
1752 | 0 | RefPtr<SourceSurfaceSkia> surface = new SourceSurfaceSkia(); |
1753 | 0 | if (surface->InitFromImage(texture, aSurface->GetFormat())) { |
1754 | 0 | return surface.forget(); |
1755 | 0 | } |
1756 | 0 | } |
1757 | 0 | |
1758 | 0 | // The data was too big to fit in a GrTexture. |
1759 | 0 | if (aSurface->GetType() == SurfaceType::SKIA) { |
1760 | 0 | // It is already a Skia source surface, so just reuse it as-is. |
1761 | 0 | RefPtr<SourceSurface> surface(aSurface); |
1762 | 0 | return surface.forget(); |
1763 | 0 | } |
1764 | 0 | |
1765 | 0 | // Wrap it in a Skia source surface so that can do tiled uploads on-demand. |
1766 | 0 | RefPtr<SourceSurfaceSkia> surface = new SourceSurfaceSkia(); |
1767 | 0 | surface->InitFromImage(image); |
1768 | 0 | return surface.forget(); |
1769 | 0 | } |
1770 | | #endif |
1771 | | |
1772 | | already_AddRefed<SourceSurface> |
1773 | | DrawTargetSkia::OptimizeSourceSurfaceForUnknownAlpha(SourceSurface *aSurface) const |
1774 | 0 | { |
1775 | 0 | #ifdef USE_SKIA_GPU |
1776 | 0 | if (UsingSkiaGPU()) { |
1777 | 0 | return OptimizeGPUSourceSurface(aSurface); |
1778 | 0 | } |
1779 | 0 | #endif |
1780 | 0 | |
1781 | 0 | if (aSurface->GetType() == SurfaceType::SKIA) { |
1782 | 0 | RefPtr<SourceSurface> surface(aSurface); |
1783 | 0 | return surface.forget(); |
1784 | 0 | } |
1785 | 0 | |
1786 | 0 | RefPtr<DataSourceSurface> dataSurface = aSurface->GetDataSurface(); |
1787 | 0 | DataSourceSurface::ScopedMap map(dataSurface, DataSourceSurface::READ_WRITE); |
1788 | 0 |
|
1789 | 0 | // For plugins, GDI can sometimes just write 0 to the alpha channel |
1790 | 0 | // even for RGBX formats. In this case, we have to manually write |
1791 | 0 | // the alpha channel to make Skia happy with RGBX and in case GDI |
1792 | 0 | // writes some bad data. Luckily, this only happens on plugins. |
1793 | 0 | WriteRGBXFormat(map.GetData(), dataSurface->GetSize(), |
1794 | 0 | map.GetStride(), dataSurface->GetFormat()); |
1795 | 0 | return dataSurface.forget(); |
1796 | 0 | } |
1797 | | |
1798 | | already_AddRefed<SourceSurface> |
1799 | | DrawTargetSkia::OptimizeSourceSurface(SourceSurface *aSurface) const |
1800 | 0 | { |
1801 | 0 | #ifdef USE_SKIA_GPU |
1802 | 0 | if (UsingSkiaGPU()) { |
1803 | 0 | return OptimizeGPUSourceSurface(aSurface); |
1804 | 0 | } |
1805 | 0 | #endif |
1806 | 0 | |
1807 | 0 | if (aSurface->GetType() == SurfaceType::SKIA) { |
1808 | 0 | RefPtr<SourceSurface> surface(aSurface); |
1809 | 0 | return surface.forget(); |
1810 | 0 | } |
1811 | 0 | |
1812 | 0 | // If we're not using skia-gl then drawing doesn't require any |
1813 | 0 | // uploading, so any data surface is fine. Call GetDataSurface |
1814 | 0 | // to trigger any required readback so that it only happens |
1815 | 0 | // once. |
1816 | 0 | RefPtr<DataSourceSurface> dataSurface = aSurface->GetDataSurface(); |
1817 | | #ifdef DEBUG |
1818 | | DataSourceSurface::ScopedMap map(dataSurface, DataSourceSurface::READ); |
1819 | | MOZ_ASSERT(VerifyRGBXFormat(map.GetData(), dataSurface->GetSize(), |
1820 | | map.GetStride(), dataSurface->GetFormat())); |
1821 | | #endif |
1822 | | return dataSurface.forget(); |
1823 | 0 | } |
1824 | | |
1825 | | #ifdef USE_SKIA_GPU |
1826 | | static inline GrGLenum |
1827 | | GfxFormatToGrGLFormat(SurfaceFormat format) |
1828 | 0 | { |
1829 | 0 | switch (format) |
1830 | 0 | { |
1831 | 0 | case SurfaceFormat::B8G8R8A8: |
1832 | 0 | return LOCAL_GL_BGRA8_EXT; |
1833 | 0 | case SurfaceFormat::B8G8R8X8: |
1834 | 0 | // We probably need to do something here. |
1835 | 0 | return LOCAL_GL_BGRA8_EXT; |
1836 | 0 | case SurfaceFormat::R5G6B5_UINT16: |
1837 | 0 | return LOCAL_GL_RGB565; |
1838 | 0 | case SurfaceFormat::A8: |
1839 | 0 | return LOCAL_GL_ALPHA8; |
1840 | 0 | default: |
1841 | 0 | return LOCAL_GL_RGBA8; |
1842 | 0 | } |
1843 | 0 | } |
1844 | | #endif |
1845 | | |
1846 | | already_AddRefed<SourceSurface> |
1847 | | DrawTargetSkia::CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const |
1848 | 0 | { |
1849 | 0 | #ifdef USE_SKIA_GPU |
1850 | 0 | if (aSurface.mType == NativeSurfaceType::OPENGL_TEXTURE && UsingSkiaGPU()) { |
1851 | 0 | // Wrap the OpenGL texture id in a Skia texture handle. |
1852 | 0 | GrGLTextureInfo texInfo; |
1853 | 0 | texInfo.fTarget = LOCAL_GL_TEXTURE_2D; |
1854 | 0 | texInfo.fID = (GrGLuint)(uintptr_t)aSurface.mSurface; |
1855 | 0 | texInfo.fFormat = GfxFormatToGrGLFormat(aSurface.mFormat); |
1856 | 0 | GrBackendTexture texDesc(aSurface.mSize.width, |
1857 | 0 | aSurface.mSize.height, |
1858 | 0 | GrMipMapped::kNo, |
1859 | 0 | texInfo); |
1860 | 0 | sk_sp<SkImage> texture = |
1861 | 0 | SkImage::MakeFromAdoptedTexture(mGrContext.get(), texDesc, |
1862 | 0 | kTopLeft_GrSurfaceOrigin, |
1863 | 0 | GfxFormatToSkiaColorType(aSurface.mFormat), |
1864 | 0 | GfxFormatToSkiaAlphaType(aSurface.mFormat)); |
1865 | 0 | RefPtr<SourceSurfaceSkia> newSurf = new SourceSurfaceSkia(); |
1866 | 0 | if (texture && newSurf->InitFromImage(texture, aSurface.mFormat)) { |
1867 | 0 | return newSurf.forget(); |
1868 | 0 | } |
1869 | 0 | return nullptr; |
1870 | 0 | } |
1871 | 0 | #endif |
1872 | 0 | |
1873 | 0 | return nullptr; |
1874 | 0 | } |
1875 | | |
1876 | | void |
1877 | | DrawTargetSkia::CopySurface(SourceSurface *aSurface, |
1878 | | const IntRect& aSourceRect, |
1879 | | const IntPoint &aDestination) |
1880 | 0 | { |
1881 | 0 | MarkChanged(); |
1882 | 0 |
|
1883 | 0 | sk_sp<SkImage> image = GetSkImageForSurface(aSurface); |
1884 | 0 | if (!image) { |
1885 | 0 | return; |
1886 | 0 | } |
1887 | 0 | |
1888 | 0 | mCanvas->save(); |
1889 | 0 | mCanvas->setMatrix(SkMatrix::MakeTrans(SkIntToScalar(aDestination.x), SkIntToScalar(aDestination.y))); |
1890 | 0 | mCanvas->clipRect(SkRect::MakeIWH(aSourceRect.Width(), aSourceRect.Height()), SkClipOp::kReplace_deprecated); |
1891 | 0 |
|
1892 | 0 | SkPaint paint; |
1893 | 0 | if (!image->isOpaque()) { |
1894 | 0 | // Keep the xfermode as SOURCE_OVER for opaque bitmaps |
1895 | 0 | // http://code.google.com/p/skia/issues/detail?id=628 |
1896 | 0 | paint.setBlendMode(SkBlendMode::kSrc); |
1897 | 0 | } |
1898 | 0 | // drawImage with A8 images ends up doing a mask operation |
1899 | 0 | // so we need to clear before |
1900 | 0 | if (image->isAlphaOnly()) { |
1901 | 0 | mCanvas->clear(SK_ColorTRANSPARENT); |
1902 | 0 | } |
1903 | 0 | mCanvas->drawImage(image, -SkIntToScalar(aSourceRect.X()), -SkIntToScalar(aSourceRect.Y()), &paint); |
1904 | 0 | mCanvas->restore(); |
1905 | 0 | } |
1906 | | |
1907 | | bool |
1908 | | DrawTargetSkia::Init(const IntSize &aSize, SurfaceFormat aFormat) |
1909 | 0 | { |
1910 | 0 | if (size_t(std::max(aSize.width, aSize.height)) > GetMaxSurfaceSize()) { |
1911 | 0 | return false; |
1912 | 0 | } |
1913 | 0 | |
1914 | 0 | // we need to have surfaces that have a stride aligned to 4 for interop with cairo |
1915 | 0 | SkImageInfo info = MakeSkiaImageInfo(aSize, aFormat); |
1916 | 0 | size_t stride = SkAlign4(info.minRowBytes()); |
1917 | 0 | mSurface = SkSurface::MakeRaster(info, stride, nullptr); |
1918 | 0 | if (!mSurface) { |
1919 | 0 | return false; |
1920 | 0 | } |
1921 | 0 | |
1922 | 0 | mSize = aSize; |
1923 | 0 | mFormat = aFormat; |
1924 | 0 | mCanvas = mSurface->getCanvas(); |
1925 | 0 | SetPermitSubpixelAA(IsOpaque(mFormat)); |
1926 | 0 |
|
1927 | 0 | if (info.isOpaque()) { |
1928 | 0 | mCanvas->clear(SK_ColorBLACK); |
1929 | 0 | } |
1930 | 0 | return true; |
1931 | 0 | } |
1932 | | |
1933 | | bool |
1934 | | DrawTargetSkia::Init(SkCanvas* aCanvas) |
1935 | 0 | { |
1936 | 0 | mCanvas = aCanvas; |
1937 | 0 |
|
1938 | 0 | SkImageInfo imageInfo = mCanvas->imageInfo(); |
1939 | 0 |
|
1940 | 0 | // If the canvas is backed by pixels we clear it to be on the safe side. If |
1941 | 0 | // it's not (for example, for PDF output) we don't. |
1942 | 0 | if (IsBackedByPixels(mCanvas)) { |
1943 | 0 | SkColor clearColor = imageInfo.isOpaque() ? SK_ColorBLACK : SK_ColorTRANSPARENT; |
1944 | 0 | mCanvas->clear(clearColor); |
1945 | 0 | } |
1946 | 0 |
|
1947 | 0 | SkISize size = mCanvas->getBaseLayerSize(); |
1948 | 0 | mSize.width = size.width(); |
1949 | 0 | mSize.height = size.height(); |
1950 | 0 | mFormat = SkiaColorTypeToGfxFormat(imageInfo.colorType(), |
1951 | 0 | imageInfo.alphaType()); |
1952 | 0 | SetPermitSubpixelAA(IsOpaque(mFormat)); |
1953 | 0 | return true; |
1954 | 0 | } |
1955 | | |
1956 | | #ifdef USE_SKIA_GPU |
1957 | | /** Indicating a DT should be cached means that space will be reserved in Skia's cache |
1958 | | * for the render target at creation time, with any unused resources exceeding the cache |
1959 | | * limits being purged. When the DT is freed, it will then be guaranteed to be kept around |
1960 | | * for subsequent allocations until it gets incidentally purged. |
1961 | | * |
1962 | | * If it is not marked as cached, no space will be purged to make room for the render |
1963 | | * target in the cache. When the DT is freed, If there is space within the resource limits |
1964 | | * it may be added to the cache, otherwise it will be freed immediately if the cache is |
1965 | | * already full. |
1966 | | * |
1967 | | * If you want to ensure that the resources will be kept around for reuse, it is better |
1968 | | * to mark them as cached. Such resources should be short-lived to ensure they don't |
1969 | | * permanently tie up cache resource limits. Long-lived resources should generally be |
1970 | | * left as uncached. |
1971 | | * |
1972 | | * In neither case will cache resource limits affect whether the resource allocation |
1973 | | * succeeds. The amount of in-use GPU resources is allowed to exceed the size of the cache. |
1974 | | * Thus, only hard GPU out-of-memory conditions will cause resource allocation to fail. |
1975 | | */ |
1976 | | bool |
1977 | | DrawTargetSkia::InitWithGrContext(GrContext* aGrContext, |
1978 | | const IntSize &aSize, |
1979 | | SurfaceFormat aFormat, |
1980 | | bool aCached) |
1981 | 0 | { |
1982 | 0 | MOZ_ASSERT(aGrContext, "null GrContext"); |
1983 | 0 |
|
1984 | 0 | if (size_t(std::max(aSize.width, aSize.height)) > GetMaxSurfaceSize()) { |
1985 | 0 | return false; |
1986 | 0 | } |
1987 | 0 | |
1988 | 0 | // Create a GPU rendertarget/texture using the supplied GrContext. |
1989 | 0 | // MakeRenderTarget also implicitly clears the underlying texture on creation. |
1990 | 0 | mSurface = |
1991 | 0 | SkSurface::MakeRenderTarget(aGrContext, |
1992 | 0 | SkBudgeted(aCached), |
1993 | 0 | MakeSkiaImageInfo(aSize, aFormat)); |
1994 | 0 | if (!mSurface) { |
1995 | 0 | return false; |
1996 | 0 | } |
1997 | 0 | |
1998 | 0 | mGrContext = sk_ref_sp(aGrContext); |
1999 | 0 | mSize = aSize; |
2000 | 0 | mFormat = aFormat; |
2001 | 0 | mCanvas = mSurface->getCanvas(); |
2002 | 0 | SetPermitSubpixelAA(IsOpaque(mFormat)); |
2003 | 0 | return true; |
2004 | 0 | } |
2005 | | |
2006 | | #endif |
2007 | | |
2008 | | bool |
2009 | | DrawTargetSkia::Init(unsigned char* aData, const IntSize &aSize, int32_t aStride, SurfaceFormat aFormat, bool aUninitialized) |
2010 | 0 | { |
2011 | 0 | MOZ_ASSERT((aFormat != SurfaceFormat::B8G8R8X8) || |
2012 | 0 | aUninitialized || VerifyRGBXFormat(aData, aSize, aStride, aFormat)); |
2013 | 0 |
|
2014 | 0 | mSurface = SkSurface::MakeRasterDirect(MakeSkiaImageInfo(aSize, aFormat), aData, aStride); |
2015 | 0 | if (!mSurface) { |
2016 | 0 | return false; |
2017 | 0 | } |
2018 | 0 | |
2019 | 0 | mSize = aSize; |
2020 | 0 | mFormat = aFormat; |
2021 | 0 | mCanvas = mSurface->getCanvas(); |
2022 | 0 | SetPermitSubpixelAA(IsOpaque(mFormat)); |
2023 | 0 | return true; |
2024 | 0 | } |
2025 | | |
2026 | | void |
2027 | | DrawTargetSkia::SetTransform(const Matrix& aTransform) |
2028 | 0 | { |
2029 | 0 | SkMatrix mat; |
2030 | 0 | GfxMatrixToSkiaMatrix(aTransform, mat); |
2031 | 0 | mCanvas->setMatrix(mat); |
2032 | 0 | mTransform = aTransform; |
2033 | 0 | } |
2034 | | |
2035 | | void* |
2036 | | DrawTargetSkia::GetNativeSurface(NativeSurfaceType aType) |
2037 | 0 | { |
2038 | 0 | #ifdef USE_SKIA_GPU |
2039 | 0 | if (aType == NativeSurfaceType::OPENGL_TEXTURE && mSurface) { |
2040 | 0 | GrBackendObject handle = mSurface->getTextureHandle(SkSurface::kFlushRead_BackendHandleAccess); |
2041 | 0 | if (handle) { |
2042 | 0 | return (void*)(uintptr_t)reinterpret_cast<GrGLTextureInfo *>(handle)->fID; |
2043 | 0 | } |
2044 | 0 | } |
2045 | 0 | #endif |
2046 | 0 | return nullptr; |
2047 | 0 | } |
2048 | | |
2049 | | |
2050 | | already_AddRefed<PathBuilder> |
2051 | | DrawTargetSkia::CreatePathBuilder(FillRule aFillRule) const |
2052 | 0 | { |
2053 | 0 | return MakeAndAddRef<PathBuilderSkia>(aFillRule); |
2054 | 0 | } |
2055 | | |
2056 | | void |
2057 | | DrawTargetSkia::ClearRect(const Rect &aRect) |
2058 | 0 | { |
2059 | 0 | MarkChanged(); |
2060 | 0 | mCanvas->save(); |
2061 | 0 | mCanvas->clipRect(RectToSkRect(aRect), SkClipOp::kIntersect, true); |
2062 | 0 | SkColor clearColor = (mFormat == SurfaceFormat::B8G8R8X8) ? SK_ColorBLACK : SK_ColorTRANSPARENT; |
2063 | 0 | mCanvas->clear(clearColor); |
2064 | 0 | mCanvas->restore(); |
2065 | 0 | } |
2066 | | |
2067 | | void |
2068 | | DrawTargetSkia::PushClip(const Path *aPath) |
2069 | 0 | { |
2070 | 0 | if (aPath->GetBackendType() != BackendType::SKIA) { |
2071 | 0 | return; |
2072 | 0 | } |
2073 | 0 | |
2074 | 0 | const PathSkia *skiaPath = static_cast<const PathSkia*>(aPath); |
2075 | 0 | mCanvas->save(); |
2076 | 0 | mCanvas->clipPath(skiaPath->GetPath(), SkClipOp::kIntersect, true); |
2077 | 0 | } |
2078 | | |
2079 | | void |
2080 | | DrawTargetSkia::PushDeviceSpaceClipRects(const IntRect* aRects, uint32_t aCount) |
2081 | 0 | { |
2082 | 0 | // Build a region by unioning all the rects together. |
2083 | 0 | SkRegion region; |
2084 | 0 | for (uint32_t i = 0; i < aCount; i++) { |
2085 | 0 | region.op(IntRectToSkIRect(aRects[i]), SkRegion::kUnion_Op); |
2086 | 0 | } |
2087 | 0 |
|
2088 | 0 | // Clip with the resulting region. clipRegion does not transform |
2089 | 0 | // this region by the current transform, unlike the other SkCanvas |
2090 | 0 | // clip methods, so it is just passed through in device-space. |
2091 | 0 | mCanvas->save(); |
2092 | 0 | mCanvas->clipRegion(region, SkClipOp::kIntersect); |
2093 | 0 | } |
2094 | | |
2095 | | void |
2096 | | DrawTargetSkia::PushClipRect(const Rect& aRect) |
2097 | 0 | { |
2098 | 0 | SkRect rect = RectToSkRect(aRect); |
2099 | 0 |
|
2100 | 0 | mCanvas->save(); |
2101 | 0 | mCanvas->clipRect(rect, SkClipOp::kIntersect, true); |
2102 | 0 | } |
2103 | | |
2104 | | void |
2105 | | DrawTargetSkia::PopClip() |
2106 | 0 | { |
2107 | 0 | mCanvas->restore(); |
2108 | 0 | SetTransform(GetTransform()); |
2109 | 0 | } |
2110 | | |
2111 | | void |
2112 | | DrawTargetSkia::PushLayer(bool aOpaque, Float aOpacity, SourceSurface* aMask, |
2113 | | const Matrix& aMaskTransform, const IntRect& aBounds, |
2114 | | bool aCopyBackground) |
2115 | 0 | { |
2116 | 0 | PushLayerWithBlend(aOpaque, aOpacity, aMask, aMaskTransform, aBounds, aCopyBackground, CompositionOp::OP_OVER); |
2117 | 0 | } |
2118 | | |
2119 | | void |
2120 | | DrawTargetSkia::PushLayerWithBlend(bool aOpaque, Float aOpacity, SourceSurface* aMask, |
2121 | | const Matrix& aMaskTransform, const IntRect& aBounds, |
2122 | | bool aCopyBackground, CompositionOp aCompositionOp) |
2123 | 0 | { |
2124 | 0 | PushedLayer layer(GetPermitSubpixelAA(), aMask); |
2125 | 0 | mPushedLayers.push_back(layer); |
2126 | 0 |
|
2127 | 0 | SkPaint paint; |
2128 | 0 |
|
2129 | 0 | paint.setAlpha(ColorFloatToByte(aOpacity)); |
2130 | 0 | paint.setBlendMode(GfxOpToSkiaOp(aCompositionOp)); |
2131 | 0 |
|
2132 | 0 | // aBounds is supplied in device space, but SaveLayerRec wants local space. |
2133 | 0 | SkRect bounds = IntRectToSkRect(aBounds); |
2134 | 0 | if (!bounds.isEmpty()) { |
2135 | 0 | SkMatrix inverseCTM; |
2136 | 0 | if (mCanvas->getTotalMatrix().invert(&inverseCTM)) { |
2137 | 0 | inverseCTM.mapRect(&bounds); |
2138 | 0 | } else { |
2139 | 0 | bounds.setEmpty(); |
2140 | 0 | } |
2141 | 0 | } |
2142 | 0 |
|
2143 | 0 | sk_sp<SkImage> clipImage = aMask ? GetSkImageForSurface(aMask) : nullptr; |
2144 | 0 | SkMatrix clipMatrix; |
2145 | 0 | GfxMatrixToSkiaMatrix(aMaskTransform, clipMatrix); |
2146 | 0 | SkCanvas::SaveLayerRec saveRec(aBounds.IsEmpty() ? nullptr : &bounds, |
2147 | 0 | &paint, |
2148 | 0 | nullptr, |
2149 | 0 | clipImage.get(), |
2150 | 0 | &clipMatrix, |
2151 | 0 | SkCanvas::kPreserveLCDText_SaveLayerFlag | |
2152 | 0 | (aCopyBackground ? SkCanvas::kInitWithPrevious_SaveLayerFlag : 0)); |
2153 | 0 |
|
2154 | 0 | mCanvas->saveLayer(saveRec); |
2155 | 0 |
|
2156 | 0 | SetPermitSubpixelAA(aOpaque); |
2157 | 0 |
|
2158 | | #ifdef MOZ_WIDGET_COCOA |
2159 | | CGContextRelease(mCG); |
2160 | | mCG = nullptr; |
2161 | | #endif |
2162 | | } |
2163 | | |
2164 | | void |
2165 | | DrawTargetSkia::PopLayer() |
2166 | 0 | { |
2167 | 0 | MarkChanged(); |
2168 | 0 |
|
2169 | 0 | MOZ_ASSERT(mPushedLayers.size()); |
2170 | 0 | const PushedLayer& layer = mPushedLayers.back(); |
2171 | 0 |
|
2172 | 0 | mCanvas->restore(); |
2173 | 0 |
|
2174 | 0 | SetTransform(GetTransform()); |
2175 | 0 | SetPermitSubpixelAA(layer.mOldPermitSubpixelAA); |
2176 | 0 |
|
2177 | 0 | mPushedLayers.pop_back(); |
2178 | 0 |
|
2179 | | #ifdef MOZ_WIDGET_COCOA |
2180 | | CGContextRelease(mCG); |
2181 | | mCG = nullptr; |
2182 | | #endif |
2183 | | } |
2184 | | |
2185 | | already_AddRefed<GradientStops> |
2186 | | DrawTargetSkia::CreateGradientStops(GradientStop *aStops, uint32_t aNumStops, ExtendMode aExtendMode) const |
2187 | 0 | { |
2188 | 0 | std::vector<GradientStop> stops; |
2189 | 0 | stops.resize(aNumStops); |
2190 | 0 | for (uint32_t i = 0; i < aNumStops; i++) { |
2191 | 0 | stops[i] = aStops[i]; |
2192 | 0 | } |
2193 | 0 | std::stable_sort(stops.begin(), stops.end()); |
2194 | 0 |
|
2195 | 0 | return MakeAndAddRef<GradientStopsSkia>(stops, aNumStops, aExtendMode); |
2196 | 0 | } |
2197 | | |
2198 | | already_AddRefed<FilterNode> |
2199 | | DrawTargetSkia::CreateFilter(FilterType aType) |
2200 | 0 | { |
2201 | 0 | return FilterNodeSoftware::Create(aType); |
2202 | 0 | } |
2203 | | |
2204 | | void |
2205 | | DrawTargetSkia::MarkChanged() |
2206 | 0 | { |
2207 | 0 | // I'm not entirely certain whether this lock is needed, as multiple threads |
2208 | 0 | // should never modify the DrawTarget at the same time anyway, but this seems |
2209 | 0 | // like the safest. |
2210 | 0 | MutexAutoLock lock(mSnapshotLock); |
2211 | 0 | if (mSnapshot) { |
2212 | 0 | if (mSnapshot->hasOneRef()) { |
2213 | 0 | // No owners outside of this DrawTarget's own reference. Just dump it. |
2214 | 0 | mSnapshot = nullptr; |
2215 | 0 | return; |
2216 | 0 | } |
2217 | 0 | |
2218 | 0 | mSnapshot->DrawTargetWillChange(); |
2219 | 0 | mSnapshot = nullptr; |
2220 | 0 |
|
2221 | 0 | // Handle copying of any image snapshots bound to the surface. |
2222 | 0 | if (mSurface) { |
2223 | 0 | mSurface->notifyContentWillChange(SkSurface::kRetain_ContentChangeMode); |
2224 | 0 | } |
2225 | 0 | } |
2226 | 0 | } |
2227 | | |
2228 | | } // namespace gfx |
2229 | | } // namespace mozilla |