/src/mozilla-central/gfx/thebes/gfxUtils.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
2 | | * This Source Code Form is subject to the terms of the Mozilla Public |
3 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
4 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
5 | | |
6 | | #include "gfxUtils.h" |
7 | | |
8 | | #include "cairo.h" |
9 | | #include "gfxContext.h" |
10 | | #include "gfxEnv.h" |
11 | | #include "gfxImageSurface.h" |
12 | | #include "gfxPlatform.h" |
13 | | #include "gfxDrawable.h" |
14 | | #include "gfxQuad.h" |
15 | | #include "imgIEncoder.h" |
16 | | #include "mozilla/Base64.h" |
17 | | #include "mozilla/dom/ImageEncoder.h" |
18 | | #include "mozilla/dom/WorkerPrivate.h" |
19 | | #include "mozilla/dom/WorkerRunnable.h" |
20 | | #include "mozilla/gfx/2D.h" |
21 | | #include "mozilla/gfx/DataSurfaceHelpers.h" |
22 | | #include "mozilla/gfx/Logging.h" |
23 | | #include "mozilla/gfx/PathHelpers.h" |
24 | | #include "mozilla/gfx/Swizzle.h" |
25 | | #include "mozilla/gfx/gfxVars.h" |
26 | | #include "mozilla/Maybe.h" |
27 | | #include "mozilla/RefPtr.h" |
28 | | #include "mozilla/UniquePtrExtensions.h" |
29 | | #include "mozilla/Unused.h" |
30 | | #include "mozilla/Vector.h" |
31 | | #include "mozilla/webrender/webrender_ffi.h" |
32 | | #include "nsAppRunner.h" |
33 | | #include "nsComponentManagerUtils.h" |
34 | | #include "nsIClipboardHelper.h" |
35 | | #include "nsIFile.h" |
36 | | #include "nsIGfxInfo.h" |
37 | | #include "nsIPresShell.h" |
38 | | #include "nsPresContext.h" |
39 | | #include "nsRegion.h" |
40 | | #include "nsServiceManagerUtils.h" |
41 | | #include "GeckoProfiler.h" |
42 | | #include "ImageContainer.h" |
43 | | #include "ImageRegion.h" |
44 | | #include "gfx2DGlue.h" |
45 | | #include "gfxPrefs.h" |
46 | | |
47 | | #ifdef XP_WIN |
48 | | #include "gfxWindowsPlatform.h" |
49 | | #endif |
50 | | |
51 | | using namespace mozilla; |
52 | | using namespace mozilla::image; |
53 | | using namespace mozilla::layers; |
54 | | using namespace mozilla::gfx; |
55 | | |
56 | | #undef compress |
57 | | #include "mozilla/Compression.h" |
58 | | |
59 | | using namespace mozilla::Compression; |
60 | | extern "C" { |
61 | | |
62 | | /** |
63 | | * Dump a raw image to the default log. This function is exported |
64 | | * from libxul, so it can be called from any library in addition to |
65 | | * (of course) from a debugger. |
66 | | * |
67 | | * Note: this helper currently assumes that all 2-bytepp images are |
68 | | * r5g6b5, and that all 4-bytepp images are r8g8b8a8. |
69 | | */ |
70 | | NS_EXPORT |
71 | | void mozilla_dump_image(void* bytes, int width, int height, int bytepp, |
72 | | int strideBytes) |
73 | 0 | { |
74 | 0 | if (0 == strideBytes) { |
75 | 0 | strideBytes = width * bytepp; |
76 | 0 | } |
77 | 0 | SurfaceFormat format; |
78 | 0 | // TODO more flexible; parse string? |
79 | 0 | switch (bytepp) { |
80 | 0 | case 2: |
81 | 0 | format = SurfaceFormat::R5G6B5_UINT16; |
82 | 0 | break; |
83 | 0 | case 4: |
84 | 0 | default: |
85 | 0 | format = SurfaceFormat::R8G8B8A8; |
86 | 0 | break; |
87 | 0 | } |
88 | 0 | |
89 | 0 | RefPtr<DataSourceSurface> surf = |
90 | 0 | Factory::CreateWrappingDataSourceSurface((uint8_t*)bytes, strideBytes, |
91 | 0 | IntSize(width, height), |
92 | 0 | format); |
93 | 0 | gfxUtils::DumpAsDataURI(surf); |
94 | 0 | } |
95 | | |
96 | | } |
97 | | |
98 | | static bool |
99 | | MapSrcDest(DataSourceSurface* srcSurf, |
100 | | DataSourceSurface* destSurf, |
101 | | DataSourceSurface::MappedSurface* out_srcMap, |
102 | | DataSourceSurface::MappedSurface* out_destMap) |
103 | 0 | { |
104 | 0 | MOZ_ASSERT(srcSurf && destSurf); |
105 | 0 | MOZ_ASSERT(out_srcMap && out_destMap); |
106 | 0 |
|
107 | 0 | if (srcSurf->GetSize() != destSurf->GetSize()) { |
108 | 0 | MOZ_ASSERT(false, "Width and height must match."); |
109 | 0 | return false; |
110 | 0 | } |
111 | 0 |
|
112 | 0 | if (srcSurf == destSurf) { |
113 | 0 | DataSourceSurface::MappedSurface map; |
114 | 0 | if (!srcSurf->Map(DataSourceSurface::MapType::READ_WRITE, &map)) { |
115 | 0 | NS_WARNING("Couldn't Map srcSurf/destSurf."); |
116 | 0 | return false; |
117 | 0 | } |
118 | 0 |
|
119 | 0 | *out_srcMap = map; |
120 | 0 | *out_destMap = map; |
121 | 0 | return true; |
122 | 0 | } |
123 | 0 | |
124 | 0 | // Map src for reading. |
125 | 0 | DataSourceSurface::MappedSurface srcMap; |
126 | 0 | if (!srcSurf->Map(DataSourceSurface::MapType::READ, &srcMap)) { |
127 | 0 | NS_WARNING("Couldn't Map srcSurf."); |
128 | 0 | return false; |
129 | 0 | } |
130 | 0 |
|
131 | 0 | // Map dest for writing. |
132 | 0 | DataSourceSurface::MappedSurface destMap; |
133 | 0 | if (!destSurf->Map(DataSourceSurface::MapType::WRITE, &destMap)) { |
134 | 0 | NS_WARNING("Couldn't Map aDest."); |
135 | 0 | srcSurf->Unmap(); |
136 | 0 | return false; |
137 | 0 | } |
138 | 0 |
|
139 | 0 | *out_srcMap = srcMap; |
140 | 0 | *out_destMap = destMap; |
141 | 0 | return true; |
142 | 0 | } |
143 | | |
144 | | static void |
145 | | UnmapSrcDest(DataSourceSurface* srcSurf, |
146 | | DataSourceSurface* destSurf) |
147 | 0 | { |
148 | 0 | if (srcSurf == destSurf) { |
149 | 0 | srcSurf->Unmap(); |
150 | 0 | } else { |
151 | 0 | srcSurf->Unmap(); |
152 | 0 | destSurf->Unmap(); |
153 | 0 | } |
154 | 0 | } |
155 | | |
156 | | bool |
157 | | gfxUtils::PremultiplyDataSurface(DataSourceSurface* srcSurf, |
158 | | DataSourceSurface* destSurf) |
159 | 0 | { |
160 | 0 | MOZ_ASSERT(srcSurf && destSurf); |
161 | 0 |
|
162 | 0 | DataSourceSurface::MappedSurface srcMap; |
163 | 0 | DataSourceSurface::MappedSurface destMap; |
164 | 0 | if (!MapSrcDest(srcSurf, destSurf, &srcMap, &destMap)) |
165 | 0 | return false; |
166 | 0 | |
167 | 0 | PremultiplyData(srcMap.mData, srcMap.mStride, srcSurf->GetFormat(), |
168 | 0 | destMap.mData, destMap.mStride, destSurf->GetFormat(), |
169 | 0 | srcSurf->GetSize()); |
170 | 0 |
|
171 | 0 | UnmapSrcDest(srcSurf, destSurf); |
172 | 0 | return true; |
173 | 0 | } |
174 | | |
175 | | bool |
176 | | gfxUtils::UnpremultiplyDataSurface(DataSourceSurface* srcSurf, |
177 | | DataSourceSurface* destSurf) |
178 | 0 | { |
179 | 0 | MOZ_ASSERT(srcSurf && destSurf); |
180 | 0 |
|
181 | 0 | DataSourceSurface::MappedSurface srcMap; |
182 | 0 | DataSourceSurface::MappedSurface destMap; |
183 | 0 | if (!MapSrcDest(srcSurf, destSurf, &srcMap, &destMap)) |
184 | 0 | return false; |
185 | 0 | |
186 | 0 | UnpremultiplyData(srcMap.mData, srcMap.mStride, srcSurf->GetFormat(), |
187 | 0 | destMap.mData, destMap.mStride, destSurf->GetFormat(), |
188 | 0 | srcSurf->GetSize()); |
189 | 0 |
|
190 | 0 | UnmapSrcDest(srcSurf, destSurf); |
191 | 0 | return true; |
192 | 0 | } |
193 | | |
194 | | static bool |
195 | | MapSrcAndCreateMappedDest(DataSourceSurface* srcSurf, |
196 | | RefPtr<DataSourceSurface>* out_destSurf, |
197 | | DataSourceSurface::MappedSurface* out_srcMap, |
198 | | DataSourceSurface::MappedSurface* out_destMap) |
199 | 0 | { |
200 | 0 | MOZ_ASSERT(srcSurf); |
201 | 0 | MOZ_ASSERT(out_destSurf && out_srcMap && out_destMap); |
202 | 0 |
|
203 | 0 | // Ok, map source for reading. |
204 | 0 | DataSourceSurface::MappedSurface srcMap; |
205 | 0 | if (!srcSurf->Map(DataSourceSurface::MapType::READ, &srcMap)) { |
206 | 0 | MOZ_ASSERT(false, "Couldn't Map srcSurf."); |
207 | 0 | return false; |
208 | 0 | } |
209 | 0 |
|
210 | 0 | // Make our dest surface based on the src. |
211 | 0 | RefPtr<DataSourceSurface> destSurf = |
212 | 0 | Factory::CreateDataSourceSurfaceWithStride(srcSurf->GetSize(), |
213 | 0 | srcSurf->GetFormat(), |
214 | 0 | srcMap.mStride); |
215 | 0 | if (NS_WARN_IF(!destSurf)) { |
216 | 0 | return false; |
217 | 0 | } |
218 | 0 | |
219 | 0 | DataSourceSurface::MappedSurface destMap; |
220 | 0 | if (!destSurf->Map(DataSourceSurface::MapType::WRITE, &destMap)) { |
221 | 0 | MOZ_ASSERT(false, "Couldn't Map destSurf."); |
222 | 0 | srcSurf->Unmap(); |
223 | 0 | return false; |
224 | 0 | } |
225 | 0 |
|
226 | 0 | *out_destSurf = destSurf; |
227 | 0 | *out_srcMap = srcMap; |
228 | 0 | *out_destMap = destMap; |
229 | 0 | return true; |
230 | 0 | } |
231 | | |
232 | | already_AddRefed<DataSourceSurface> |
233 | | gfxUtils::CreatePremultipliedDataSurface(DataSourceSurface* srcSurf) |
234 | 0 | { |
235 | 0 | RefPtr<DataSourceSurface> destSurf; |
236 | 0 | DataSourceSurface::MappedSurface srcMap; |
237 | 0 | DataSourceSurface::MappedSurface destMap; |
238 | 0 | if (!MapSrcAndCreateMappedDest(srcSurf, &destSurf, &srcMap, &destMap)) { |
239 | 0 | MOZ_ASSERT(false, "MapSrcAndCreateMappedDest failed."); |
240 | 0 | RefPtr<DataSourceSurface> surface(srcSurf); |
241 | 0 | return surface.forget(); |
242 | 0 | } |
243 | 0 |
|
244 | 0 | PremultiplyData(srcMap.mData, srcMap.mStride, srcSurf->GetFormat(), |
245 | 0 | destMap.mData, destMap.mStride, destSurf->GetFormat(), |
246 | 0 | srcSurf->GetSize()); |
247 | 0 |
|
248 | 0 | UnmapSrcDest(srcSurf, destSurf); |
249 | 0 | return destSurf.forget(); |
250 | 0 | } |
251 | | |
252 | | already_AddRefed<DataSourceSurface> |
253 | | gfxUtils::CreateUnpremultipliedDataSurface(DataSourceSurface* srcSurf) |
254 | 0 | { |
255 | 0 | RefPtr<DataSourceSurface> destSurf; |
256 | 0 | DataSourceSurface::MappedSurface srcMap; |
257 | 0 | DataSourceSurface::MappedSurface destMap; |
258 | 0 | if (!MapSrcAndCreateMappedDest(srcSurf, &destSurf, &srcMap, &destMap)) { |
259 | 0 | MOZ_ASSERT(false, "MapSrcAndCreateMappedDest failed."); |
260 | 0 | RefPtr<DataSourceSurface> surface(srcSurf); |
261 | 0 | return surface.forget(); |
262 | 0 | } |
263 | 0 |
|
264 | 0 | UnpremultiplyData(srcMap.mData, srcMap.mStride, srcSurf->GetFormat(), |
265 | 0 | destMap.mData, destMap.mStride, destSurf->GetFormat(), |
266 | 0 | srcSurf->GetSize()); |
267 | 0 |
|
268 | 0 | UnmapSrcDest(srcSurf, destSurf); |
269 | 0 | return destSurf.forget(); |
270 | 0 | } |
271 | | |
272 | | void |
273 | | gfxUtils::ConvertBGRAtoRGBA(uint8_t* aData, uint32_t aLength) |
274 | 0 | { |
275 | 0 | MOZ_ASSERT((aLength % 4) == 0, "Loop below will pass srcEnd!"); |
276 | 0 | SwizzleData(aData, aLength, SurfaceFormat::B8G8R8A8, |
277 | 0 | aData, aLength, SurfaceFormat::R8G8B8A8, |
278 | 0 | IntSize(aLength / 4, 1)); |
279 | 0 | } |
280 | | |
281 | | #if !defined(MOZ_GFX_OPTIMIZE_MOBILE) |
282 | | /** |
283 | | * This returns the fastest operator to use for solid surfaces which have no |
284 | | * alpha channel or their alpha channel is uniformly opaque. |
285 | | * This differs per render mode. |
286 | | */ |
287 | | static CompositionOp |
288 | | OptimalFillOp() |
289 | 0 | { |
290 | | #ifdef XP_WIN |
291 | | if (gfxWindowsPlatform::GetPlatform()->IsDirect2DBackend()) { |
292 | | // D2D -really- hates operator source. |
293 | | return CompositionOp::OP_OVER; |
294 | | } |
295 | | #endif |
296 | | return CompositionOp::OP_SOURCE; |
297 | 0 | } |
298 | | |
299 | | // EXTEND_PAD won't help us here; we have to create a temporary surface to hold |
300 | | // the subimage of pixels we're allowed to sample. |
301 | | static already_AddRefed<gfxDrawable> |
302 | | CreateSamplingRestrictedDrawable(gfxDrawable* aDrawable, |
303 | | gfxContext* aContext, |
304 | | const ImageRegion& aRegion, |
305 | | const SurfaceFormat aFormat, |
306 | | bool aUseOptimalFillOp) |
307 | 0 | { |
308 | 0 | AUTO_PROFILER_LABEL("CreateSamplingRestrictedDrawable", GRAPHICS); |
309 | 0 |
|
310 | 0 | DrawTarget* destDrawTarget = aContext->GetDrawTarget(); |
311 | 0 | if (destDrawTarget->GetBackendType() == BackendType::DIRECT2D1_1) { |
312 | 0 | return nullptr; |
313 | 0 | } |
314 | 0 | |
315 | 0 | gfxRect clipExtents = aContext->GetClipExtents(); |
316 | 0 |
|
317 | 0 | // Inflate by one pixel because bilinear filtering will sample at most |
318 | 0 | // one pixel beyond the computed image pixel coordinate. |
319 | 0 | clipExtents.Inflate(1.0); |
320 | 0 |
|
321 | 0 | gfxRect needed = aRegion.IntersectAndRestrict(clipExtents); |
322 | 0 | needed.RoundOut(); |
323 | 0 |
|
324 | 0 | // if 'needed' is empty, nothing will be drawn since aFill |
325 | 0 | // must be entirely outside the clip region, so it doesn't |
326 | 0 | // matter what we do here, but we should avoid trying to |
327 | 0 | // create a zero-size surface. |
328 | 0 | if (needed.IsEmpty()) |
329 | 0 | return nullptr; |
330 | 0 | |
331 | 0 | IntSize size(int32_t(needed.Width()), int32_t(needed.Height())); |
332 | 0 |
|
333 | 0 | RefPtr<DrawTarget> target = |
334 | 0 | gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(size, aFormat); |
335 | 0 | if (!target || !target->IsValid()) { |
336 | 0 | return nullptr; |
337 | 0 | } |
338 | 0 | |
339 | 0 | RefPtr<gfxContext> tmpCtx = gfxContext::CreateOrNull(target); |
340 | 0 | MOZ_ASSERT(tmpCtx); // already checked the target above |
341 | 0 |
|
342 | 0 | if (aUseOptimalFillOp) { |
343 | 0 | tmpCtx->SetOp(OptimalFillOp()); |
344 | 0 | } |
345 | 0 | aDrawable->Draw(tmpCtx, needed - needed.TopLeft(), ExtendMode::REPEAT, |
346 | 0 | SamplingFilter::LINEAR, |
347 | 0 | 1.0, gfxMatrix::Translation(needed.TopLeft())); |
348 | 0 | RefPtr<SourceSurface> surface = target->Snapshot(); |
349 | 0 |
|
350 | 0 | RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(surface, size, gfxMatrix::Translation(-needed.TopLeft())); |
351 | 0 | return drawable.forget(); |
352 | 0 | } |
353 | | #endif // !MOZ_GFX_OPTIMIZE_MOBILE |
354 | | |
355 | | /* These heuristics are based on Source/WebCore/platform/graphics/skia/ImageSkia.cpp:computeResamplingMode() */ |
356 | | #ifdef MOZ_GFX_OPTIMIZE_MOBILE |
357 | | static SamplingFilter ReduceResamplingFilter(SamplingFilter aSamplingFilter, |
358 | | int aImgWidth, int aImgHeight, |
359 | | float aSourceWidth, float aSourceHeight) |
360 | | { |
361 | | // Images smaller than this in either direction are considered "small" and |
362 | | // are not resampled ever (see below). |
363 | | const int kSmallImageSizeThreshold = 8; |
364 | | |
365 | | // The amount an image can be stretched in a single direction before we |
366 | | // say that it is being stretched so much that it must be a line or |
367 | | // background that doesn't need resampling. |
368 | | const float kLargeStretch = 3.0f; |
369 | | |
370 | | if (aImgWidth <= kSmallImageSizeThreshold |
371 | | || aImgHeight <= kSmallImageSizeThreshold) { |
372 | | // Never resample small images. These are often used for borders and |
373 | | // rules (think 1x1 images used to make lines). |
374 | | return SamplingFilter::POINT; |
375 | | } |
376 | | |
377 | | if (aImgHeight * kLargeStretch <= aSourceHeight || aImgWidth * kLargeStretch <= aSourceWidth) { |
378 | | // Large image tiling detected. |
379 | | |
380 | | // Don't resample if it is being tiled a lot in only one direction. |
381 | | // This is trying to catch cases where somebody has created a border |
382 | | // (which might be large) and then is stretching it to fill some part |
383 | | // of the page. |
384 | | if (fabs(aSourceWidth - aImgWidth)/aImgWidth < 0.5 || fabs(aSourceHeight - aImgHeight)/aImgHeight < 0.5) |
385 | | return SamplingFilter::POINT; |
386 | | |
387 | | // The image is growing a lot and in more than one direction. Resampling |
388 | | // is slow and doesn't give us very much when growing a lot. |
389 | | return aSamplingFilter; |
390 | | } |
391 | | |
392 | | /* Some notes on other heuristics: |
393 | | The Skia backend also uses nearest for backgrounds that are stretched by |
394 | | a large amount. I'm not sure this is common enough for us to worry about |
395 | | now. It also uses nearest for backgrounds/avoids high quality for images |
396 | | that are very slightly scaled. I'm also not sure that very slightly |
397 | | scaled backgrounds are common enough us to worry about. |
398 | | |
399 | | We don't currently have much support for doing high quality interpolation. |
400 | | The only place this currently happens is on Quartz and we don't have as |
401 | | much control over it as would be needed. Webkit avoids using high quality |
402 | | resampling during load. It also avoids high quality if the transformation |
403 | | is not just a scale and translation |
404 | | |
405 | | WebKit bug #40045 added code to avoid resampling different parts |
406 | | of an image with different methods by using a resampling hint size. |
407 | | It currently looks unused in WebKit but it's something to watch out for. |
408 | | */ |
409 | | |
410 | | return aSamplingFilter; |
411 | | } |
412 | | #else |
413 | | static SamplingFilter ReduceResamplingFilter(SamplingFilter aSamplingFilter, |
414 | | int aImgWidth, int aImgHeight, |
415 | | int aSourceWidth, int aSourceHeight) |
416 | 0 | { |
417 | 0 | // Just pass the filter through unchanged |
418 | 0 | return aSamplingFilter; |
419 | 0 | } |
420 | | #endif |
421 | | |
422 | | #ifdef MOZ_WIDGET_COCOA |
423 | | // Only prescale a temporary surface if we're going to repeat it often. |
424 | | // Scaling is expensive on OS X and without prescaling, we'd scale |
425 | | // every tile of the repeated rect. However, using a temp surface also potentially uses |
426 | | // more memory if the scaled image is large. So only prescale on a temp |
427 | | // surface if we know we're going to repeat the image in either the X or Y axis |
428 | | // multiple times. |
429 | | static bool |
430 | | ShouldUseTempSurface(Rect aImageRect, Rect aNeededRect) |
431 | | { |
432 | | int repeatX = aNeededRect.width / aImageRect.width; |
433 | | int repeatY = aNeededRect.height / aImageRect.height; |
434 | | return (repeatX >= 5) || (repeatY >= 5); |
435 | | } |
436 | | |
437 | | static bool |
438 | | PrescaleAndTileDrawable(gfxDrawable* aDrawable, |
439 | | gfxContext* aContext, |
440 | | const ImageRegion& aRegion, |
441 | | Rect aImageRect, |
442 | | const SamplingFilter aSamplingFilter, |
443 | | const SurfaceFormat aFormat, |
444 | | gfxFloat aOpacity, |
445 | | ExtendMode aExtendMode) |
446 | | { |
447 | | Size scaleFactor = aContext->CurrentMatrix().ScaleFactors(true); |
448 | | Matrix scaleMatrix = Matrix::Scaling(scaleFactor.width, scaleFactor.height); |
449 | | const float fuzzFactor = 0.01; |
450 | | |
451 | | // If we aren't scaling or translating, don't go down this path |
452 | | if ((FuzzyEqual(scaleFactor.width, 1.0, fuzzFactor) && |
453 | | FuzzyEqual(scaleFactor.width, 1.0, fuzzFactor)) || |
454 | | aContext->CurrentMatrix().HasNonAxisAlignedTransform()) { |
455 | | return false; |
456 | | } |
457 | | |
458 | | gfxRect clipExtents = aContext->GetClipExtents(); |
459 | | |
460 | | // Inflate by one pixel because bilinear filtering will sample at most |
461 | | // one pixel beyond the computed image pixel coordinate. |
462 | | clipExtents.Inflate(1.0); |
463 | | |
464 | | gfxRect needed = aRegion.IntersectAndRestrict(clipExtents); |
465 | | Rect scaledNeededRect = scaleMatrix.TransformBounds(ToRect(needed)); |
466 | | scaledNeededRect.RoundOut(); |
467 | | if (scaledNeededRect.IsEmpty()) { |
468 | | return false; |
469 | | } |
470 | | |
471 | | Rect scaledImageRect = scaleMatrix.TransformBounds(aImageRect); |
472 | | if (!ShouldUseTempSurface(scaledImageRect, scaledNeededRect)) { |
473 | | return false; |
474 | | } |
475 | | |
476 | | IntSize scaledImageSize((int32_t)scaledImageRect.width, |
477 | | (int32_t)scaledImageRect.height); |
478 | | if (scaledImageSize.width != scaledImageRect.width || |
479 | | scaledImageSize.height != scaledImageRect.height) { |
480 | | // If the scaled image isn't pixel aligned, we'll get artifacts |
481 | | // so we have to take the slow path. |
482 | | return false; |
483 | | } |
484 | | |
485 | | RefPtr<DrawTarget> scaledDT = |
486 | | gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(scaledImageSize, aFormat); |
487 | | if (!scaledDT || !scaledDT->IsValid()) { |
488 | | return false; |
489 | | } |
490 | | |
491 | | RefPtr<gfxContext> tmpCtx = gfxContext::CreateOrNull(scaledDT); |
492 | | MOZ_ASSERT(tmpCtx); // already checked the target above |
493 | | |
494 | | scaledDT->SetTransform(scaleMatrix); |
495 | | gfxRect gfxImageRect(aImageRect.x, aImageRect.y, aImageRect.width, aImageRect.height); |
496 | | |
497 | | // Since this is just the scaled image, we don't want to repeat anything yet. |
498 | | aDrawable->Draw(tmpCtx, gfxImageRect, ExtendMode::CLAMP, aSamplingFilter, 1.0, gfxMatrix()); |
499 | | |
500 | | RefPtr<SourceSurface> scaledImage = scaledDT->Snapshot(); |
501 | | |
502 | | { |
503 | | gfxContextMatrixAutoSaveRestore autoSR(aContext); |
504 | | Matrix withoutScale = aContext->CurrentMatrix(); |
505 | | DrawTarget* destDrawTarget = aContext->GetDrawTarget(); |
506 | | |
507 | | // The translation still is in scaled units |
508 | | withoutScale.PreScale(1.0 / scaleFactor.width, 1.0 / scaleFactor.height); |
509 | | aContext->SetMatrix(withoutScale); |
510 | | |
511 | | DrawOptions drawOptions(aOpacity, aContext->CurrentOp(), |
512 | | aContext->CurrentAntialiasMode()); |
513 | | |
514 | | SurfacePattern scaledImagePattern(scaledImage, aExtendMode, |
515 | | Matrix(), aSamplingFilter); |
516 | | destDrawTarget->FillRect(scaledNeededRect, scaledImagePattern, drawOptions); |
517 | | } |
518 | | return true; |
519 | | } |
520 | | #endif // MOZ_WIDGET_COCOA |
521 | | |
522 | | /* static */ void |
523 | | gfxUtils::DrawPixelSnapped(gfxContext* aContext, |
524 | | gfxDrawable* aDrawable, |
525 | | const gfxSize& aImageSize, |
526 | | const ImageRegion& aRegion, |
527 | | const SurfaceFormat aFormat, |
528 | | SamplingFilter aSamplingFilter, |
529 | | uint32_t aImageFlags, |
530 | | gfxFloat aOpacity, |
531 | | bool aUseOptimalFillOp) |
532 | 0 | { |
533 | 0 | AUTO_PROFILER_LABEL("gfxUtils::DrawPixelSnapped", GRAPHICS); |
534 | 0 |
|
535 | 0 | gfxRect imageRect(gfxPoint(0, 0), aImageSize); |
536 | 0 | gfxRect region(aRegion.Rect()); |
537 | 0 | ExtendMode extendMode = aRegion.GetExtendMode(); |
538 | 0 |
|
539 | 0 | RefPtr<gfxDrawable> drawable = aDrawable; |
540 | 0 |
|
541 | 0 | aSamplingFilter = |
542 | 0 | ReduceResamplingFilter(aSamplingFilter, |
543 | 0 | imageRect.Width(), imageRect.Height(), |
544 | 0 | region.Width(), region.Height()); |
545 | 0 |
|
546 | 0 | // OK now, the hard part left is to account for the subimage sampling |
547 | 0 | // restriction. If all the transforms involved are just integer |
548 | 0 | // translations, then we assume no resampling will occur so there's |
549 | 0 | // nothing to do. |
550 | 0 | // XXX if only we had source-clipping in cairo! |
551 | 0 |
|
552 | 0 | if (aContext->CurrentMatrix().HasNonIntegerTranslation()) { |
553 | 0 | if ((extendMode != ExtendMode::CLAMP) || !aRegion.RestrictionContains(imageRect)) { |
554 | 0 | if (drawable->DrawWithSamplingRect(aContext->GetDrawTarget(), |
555 | 0 | aContext->CurrentOp(), |
556 | 0 | aContext->CurrentAntialiasMode(), |
557 | 0 | aRegion.Rect(), |
558 | 0 | aRegion.Restriction(), |
559 | 0 | extendMode, aSamplingFilter, |
560 | 0 | aOpacity)) { |
561 | 0 | return; |
562 | 0 | } |
563 | 0 | |
564 | | #ifdef MOZ_WIDGET_COCOA |
565 | | if (PrescaleAndTileDrawable(aDrawable, aContext, aRegion, |
566 | | ToRect(imageRect), aSamplingFilter, |
567 | | aFormat, aOpacity, extendMode)) { |
568 | | return; |
569 | | } |
570 | | #endif |
571 | | |
572 | 0 | // On Mobile, we don't ever want to do this; it has the potential for |
573 | 0 | // allocating very large temporary surfaces, especially since we'll |
574 | 0 | // do full-page snapshots often (see bug 749426). |
575 | 0 | #if !defined(MOZ_GFX_OPTIMIZE_MOBILE) |
576 | 0 | RefPtr<gfxDrawable> restrictedDrawable = |
577 | 0 | CreateSamplingRestrictedDrawable(aDrawable, aContext, |
578 | 0 | aRegion, aFormat, |
579 | 0 | aUseOptimalFillOp); |
580 | 0 | if (restrictedDrawable) { |
581 | 0 | drawable.swap(restrictedDrawable); |
582 | 0 |
|
583 | 0 | // We no longer need to tile: Either we never needed to, or we already |
584 | 0 | // filled a surface with the tiled pattern; this surface can now be |
585 | 0 | // drawn without tiling. |
586 | 0 | extendMode = ExtendMode::CLAMP; |
587 | 0 | } |
588 | 0 | #endif |
589 | 0 | } |
590 | 0 | } |
591 | 0 |
|
592 | 0 | drawable->Draw(aContext, aRegion.Rect(), extendMode, aSamplingFilter, |
593 | 0 | aOpacity, gfxMatrix()); |
594 | 0 | } |
595 | | |
596 | | /* static */ int |
597 | | gfxUtils::ImageFormatToDepth(gfxImageFormat aFormat) |
598 | 0 | { |
599 | 0 | switch (aFormat) { |
600 | 0 | case SurfaceFormat::A8R8G8B8_UINT32: |
601 | 0 | return 32; |
602 | 0 | case SurfaceFormat::X8R8G8B8_UINT32: |
603 | 0 | return 24; |
604 | 0 | case SurfaceFormat::R5G6B5_UINT16: |
605 | 0 | return 16; |
606 | 0 | default: |
607 | 0 | break; |
608 | 0 | } |
609 | 0 | return 0; |
610 | 0 | } |
611 | | |
612 | | /*static*/ void |
613 | | gfxUtils::ClipToRegion(gfxContext* aContext, const nsIntRegion& aRegion) |
614 | 0 | { |
615 | 0 | aContext->NewPath(); |
616 | 0 | for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) { |
617 | 0 | const IntRect& r = iter.Get(); |
618 | 0 | aContext->Rectangle(gfxRect(r.X(), r.Y(), r.Width(), r.Height())); |
619 | 0 | } |
620 | 0 | aContext->Clip(); |
621 | 0 | } |
622 | | |
623 | | /*static*/ void |
624 | | gfxUtils::ClipToRegion(DrawTarget* aTarget, const nsIntRegion& aRegion) |
625 | 0 | { |
626 | 0 | uint32_t numRects = aRegion.GetNumRects(); |
627 | 0 | // If there is only one rect, then the region bounds are equivalent to the |
628 | 0 | // contents. So just use push a single clip rect with the bounds. |
629 | 0 | if (numRects == 1) { |
630 | 0 | aTarget->PushClipRect(Rect(aRegion.GetBounds())); |
631 | 0 | return; |
632 | 0 | } |
633 | 0 | |
634 | 0 | // Check if the target's transform will preserve axis-alignment and |
635 | 0 | // pixel-alignment for each rect. For now, just handle the common case |
636 | 0 | // of integer translations. |
637 | 0 | Matrix transform = aTarget->GetTransform(); |
638 | 0 | if (transform.IsIntegerTranslation()) { |
639 | 0 | IntPoint translation = RoundedToInt(transform.GetTranslation()); |
640 | 0 | AutoTArray<IntRect, 16> rects; |
641 | 0 | rects.SetLength(numRects); |
642 | 0 | uint32_t i = 0; |
643 | 0 | // Build the list of transformed rects by adding in the translation. |
644 | 0 | for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) { |
645 | 0 | IntRect rect = iter.Get(); |
646 | 0 | rect.MoveBy(translation); |
647 | 0 | rects[i++] = rect; |
648 | 0 | } |
649 | 0 | aTarget->PushDeviceSpaceClipRects(rects.Elements(), rects.Length()); |
650 | 0 | } else { |
651 | 0 | // The transform does not produce axis-aligned rects or a rect was not |
652 | 0 | // pixel-aligned. So just build a path with all the rects and clip to it |
653 | 0 | // instead. |
654 | 0 | RefPtr<PathBuilder> pathBuilder = aTarget->CreatePathBuilder(); |
655 | 0 | for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) { |
656 | 0 | AppendRectToPath(pathBuilder, Rect(iter.Get())); |
657 | 0 | } |
658 | 0 | RefPtr<Path> path = pathBuilder->Finish(); |
659 | 0 | aTarget->PushClip(path); |
660 | 0 | } |
661 | 0 | } |
662 | | |
663 | | /*static*/ float |
664 | | gfxUtils::ClampToScaleFactor(float aVal, bool aRoundDown) |
665 | 0 | { |
666 | 0 | // Arbitary scale factor limitation. We can increase this |
667 | 0 | // for better scaling performance at the cost of worse |
668 | 0 | // quality. |
669 | 0 | static const float kScaleResolution = 2; |
670 | 0 |
|
671 | 0 | // Negative scaling is just a flip and irrelevant to |
672 | 0 | // our resolution calculation. |
673 | 0 | if (aVal < 0.0) { |
674 | 0 | aVal = -aVal; |
675 | 0 | } |
676 | 0 |
|
677 | 0 | bool inverse = false; |
678 | 0 | if (aVal < 1.0) { |
679 | 0 | inverse = true; |
680 | 0 | aVal = 1 / aVal; |
681 | 0 | } |
682 | 0 |
|
683 | 0 | float power = logf(aVal)/logf(kScaleResolution); |
684 | 0 |
|
685 | 0 | // If power is within 1e-5 of an integer, round to nearest to |
686 | 0 | // prevent floating point errors, otherwise round up to the |
687 | 0 | // next integer value. |
688 | 0 | if (fabs(power - NS_round(power)) < 1e-5) { |
689 | 0 | power = NS_round(power); |
690 | 0 | // Use floor when we are either inverted or rounding down, but |
691 | 0 | // not both. |
692 | 0 | } else if (inverse != aRoundDown) { |
693 | 0 | power = floor(power); |
694 | 0 | // Otherwise, ceil when we are not inverted and not rounding |
695 | 0 | // down, or we are inverted and rounding down. |
696 | 0 | } else { |
697 | 0 | power = ceil(power); |
698 | 0 | } |
699 | 0 |
|
700 | 0 | float scale = powf(kScaleResolution, power); |
701 | 0 |
|
702 | 0 | if (inverse) { |
703 | 0 | scale = 1 / scale; |
704 | 0 | } |
705 | 0 |
|
706 | 0 | return scale; |
707 | 0 | } |
708 | | |
709 | | gfxMatrix |
710 | | gfxUtils::TransformRectToRect(const gfxRect& aFrom, const gfxPoint& aToTopLeft, |
711 | | const gfxPoint& aToTopRight, const gfxPoint& aToBottomRight) |
712 | 0 | { |
713 | 0 | gfxMatrix m; |
714 | 0 | if (aToTopRight.y == aToTopLeft.y && aToTopRight.x == aToBottomRight.x) { |
715 | 0 | // Not a rotation, so xy and yx are zero |
716 | 0 | m._21 = m._12 = 0.0; |
717 | 0 | m._11 = (aToBottomRight.x - aToTopLeft.x)/aFrom.Width(); |
718 | 0 | m._22 = (aToBottomRight.y - aToTopLeft.y)/aFrom.Height(); |
719 | 0 | m._31 = aToTopLeft.x - m._11*aFrom.X(); |
720 | 0 | m._32 = aToTopLeft.y - m._22*aFrom.Y(); |
721 | 0 | } else { |
722 | 0 | NS_ASSERTION(aToTopRight.y == aToBottomRight.y && aToTopRight.x == aToTopLeft.x, |
723 | 0 | "Destination rectangle not axis-aligned"); |
724 | 0 | m._11 = m._22 = 0.0; |
725 | 0 | m._21 = (aToBottomRight.x - aToTopLeft.x)/aFrom.Height(); |
726 | 0 | m._12 = (aToBottomRight.y - aToTopLeft.y)/aFrom.Width(); |
727 | 0 | m._31 = aToTopLeft.x - m._21*aFrom.Y(); |
728 | 0 | m._32 = aToTopLeft.y - m._12*aFrom.X(); |
729 | 0 | } |
730 | 0 | return m; |
731 | 0 | } |
732 | | |
733 | | Matrix |
734 | | gfxUtils::TransformRectToRect(const gfxRect& aFrom, const IntPoint& aToTopLeft, |
735 | | const IntPoint& aToTopRight, const IntPoint& aToBottomRight) |
736 | 0 | { |
737 | 0 | Matrix m; |
738 | 0 | if (aToTopRight.y == aToTopLeft.y && aToTopRight.x == aToBottomRight.x) { |
739 | 0 | // Not a rotation, so xy and yx are zero |
740 | 0 | m._12 = m._21 = 0.0; |
741 | 0 | m._11 = (aToBottomRight.x - aToTopLeft.x)/aFrom.Width(); |
742 | 0 | m._22 = (aToBottomRight.y - aToTopLeft.y)/aFrom.Height(); |
743 | 0 | m._31 = aToTopLeft.x - m._11*aFrom.X(); |
744 | 0 | m._32 = aToTopLeft.y - m._22*aFrom.Y(); |
745 | 0 | } else { |
746 | 0 | NS_ASSERTION(aToTopRight.y == aToBottomRight.y && aToTopRight.x == aToTopLeft.x, |
747 | 0 | "Destination rectangle not axis-aligned"); |
748 | 0 | m._11 = m._22 = 0.0; |
749 | 0 | m._21 = (aToBottomRight.x - aToTopLeft.x)/aFrom.Height(); |
750 | 0 | m._12 = (aToBottomRight.y - aToTopLeft.y)/aFrom.Width(); |
751 | 0 | m._31 = aToTopLeft.x - m._21*aFrom.Y(); |
752 | 0 | m._32 = aToTopLeft.y - m._12*aFrom.X(); |
753 | 0 | } |
754 | 0 | return m; |
755 | 0 | } |
756 | | |
757 | | /* This function is sort of shitty. We truncate doubles |
758 | | * to ints then convert those ints back to doubles to make sure that |
759 | | * they equal the doubles that we got in. */ |
760 | | bool |
761 | | gfxUtils::GfxRectToIntRect(const gfxRect& aIn, IntRect* aOut) |
762 | 0 | { |
763 | 0 | *aOut = IntRect(int32_t(aIn.X()), int32_t(aIn.Y()), |
764 | 0 | int32_t(aIn.Width()), int32_t(aIn.Height())); |
765 | 0 | return gfxRect(aOut->X(), aOut->Y(), aOut->Width(), aOut->Height()).IsEqualEdges(aIn); |
766 | 0 | } |
767 | | |
768 | | /* Clamp r to CAIRO_COORD_MIN .. CAIRO_COORD_MAX |
769 | | * these are to be device coordinates. |
770 | | * |
771 | | * Cairo is currently using 24.8 fixed point, |
772 | | * so -2^24 .. 2^24-1 is our valid |
773 | | */ |
774 | | /*static*/ void |
775 | | gfxUtils::ConditionRect(gfxRect& aRect) |
776 | 0 | { |
777 | 0 | #define CAIRO_COORD_MAX (16777215.0) |
778 | 0 | #define CAIRO_COORD_MIN (-16777216.0) |
779 | 0 | // if either x or y is way out of bounds; |
780 | 0 | // note that we don't handle negative w/h here |
781 | 0 | if (aRect.X() > CAIRO_COORD_MAX) { |
782 | 0 | aRect.SetRectX(CAIRO_COORD_MAX, 0.0); |
783 | 0 | } |
784 | 0 |
|
785 | 0 | if (aRect.Y() > CAIRO_COORD_MAX) { |
786 | 0 | aRect.SetRectY(CAIRO_COORD_MAX, 0.0); |
787 | 0 | } |
788 | 0 |
|
789 | 0 | if (aRect.X() < CAIRO_COORD_MIN) { |
790 | 0 | aRect.SetWidth(aRect.XMost() - CAIRO_COORD_MIN); |
791 | 0 | if (aRect.Width() < 0.0) { |
792 | 0 | aRect.SetWidth(0.0); |
793 | 0 | } |
794 | 0 | aRect.MoveToX(CAIRO_COORD_MIN); |
795 | 0 | } |
796 | 0 |
|
797 | 0 | if (aRect.Y() < CAIRO_COORD_MIN) { |
798 | 0 | aRect.SetHeight(aRect.YMost() - CAIRO_COORD_MIN); |
799 | 0 | if (aRect.Height() < 0.0) { |
800 | 0 | aRect.SetHeight(0.0); |
801 | 0 | } |
802 | 0 | aRect.MoveToY(CAIRO_COORD_MIN); |
803 | 0 | } |
804 | 0 |
|
805 | 0 | if (aRect.XMost() > CAIRO_COORD_MAX) { |
806 | 0 | aRect.SetRightEdge(CAIRO_COORD_MAX); |
807 | 0 | } |
808 | 0 |
|
809 | 0 | if (aRect.YMost() > CAIRO_COORD_MAX) { |
810 | 0 | aRect.SetBottomEdge(CAIRO_COORD_MAX); |
811 | 0 | } |
812 | 0 | #undef CAIRO_COORD_MAX |
813 | 0 | #undef CAIRO_COORD_MIN |
814 | 0 | } |
815 | | |
816 | | /*static*/ gfxQuad |
817 | | gfxUtils::TransformToQuad(const gfxRect& aRect, |
818 | | const mozilla::gfx::Matrix4x4 &aMatrix) |
819 | 0 | { |
820 | 0 | gfxPoint points[4]; |
821 | 0 |
|
822 | 0 | points[0] = aMatrix.TransformPoint(aRect.TopLeft()); |
823 | 0 | points[1] = aMatrix.TransformPoint(aRect.TopRight()); |
824 | 0 | points[2] = aMatrix.TransformPoint(aRect.BottomRight()); |
825 | 0 | points[3] = aMatrix.TransformPoint(aRect.BottomLeft()); |
826 | 0 |
|
827 | 0 | // Could this ever result in lines that intersect? I don't think so. |
828 | 0 | return gfxQuad(points[0], points[1], points[2], points[3]); |
829 | 0 | } |
830 | | |
831 | | /* static */ void gfxUtils::ClearThebesSurface(gfxASurface* aSurface) |
832 | 0 | { |
833 | 0 | if (aSurface->CairoStatus()) { |
834 | 0 | return; |
835 | 0 | } |
836 | 0 | cairo_surface_t* surf = aSurface->CairoSurface(); |
837 | 0 | if (cairo_surface_status(surf)) { |
838 | 0 | return; |
839 | 0 | } |
840 | 0 | cairo_t* ctx = cairo_create(surf); |
841 | 0 | cairo_set_source_rgba(ctx, 0.0, 0.0, 0.0, 0.0); |
842 | 0 | cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE); |
843 | 0 | IntRect bounds(nsIntPoint(0, 0), aSurface->GetSize()); |
844 | 0 | cairo_rectangle(ctx, bounds.X(), bounds.Y(), bounds.Width(), bounds.Height()); |
845 | 0 | cairo_fill(ctx); |
846 | 0 | cairo_destroy(ctx); |
847 | 0 | } |
848 | | |
849 | | /* static */ already_AddRefed<DataSourceSurface> |
850 | | gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(SourceSurface* aSurface, |
851 | | SurfaceFormat aFormat) |
852 | 0 | { |
853 | 0 | MOZ_ASSERT(aFormat != aSurface->GetFormat(), |
854 | 0 | "Unnecessary - and very expersive - surface format conversion"); |
855 | 0 |
|
856 | 0 | Rect bounds(0, 0, aSurface->GetSize().width, aSurface->GetSize().height); |
857 | 0 |
|
858 | 0 | if (!aSurface->IsDataSourceSurface()) { |
859 | 0 | // If the surface is NOT of type DATA then its data is not mapped into main |
860 | 0 | // memory. Format conversion is probably faster on the GPU, and by doing it |
861 | 0 | // there we can avoid any expensive uploads/readbacks except for (possibly) |
862 | 0 | // a single readback due to the unavoidable GetDataSurface() call. Using |
863 | 0 | // CreateOffscreenContentDrawTarget ensures the conversion happens on the |
864 | 0 | // GPU. |
865 | 0 | RefPtr<DrawTarget> dt = gfxPlatform::GetPlatform()-> |
866 | 0 | CreateOffscreenContentDrawTarget(aSurface->GetSize(), aFormat); |
867 | 0 | if (!dt) { |
868 | 0 | gfxWarning() << "gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat failed in CreateOffscreenContentDrawTarget"; |
869 | 0 | return nullptr; |
870 | 0 | } |
871 | 0 |
|
872 | 0 | // Using DrawSurface() here rather than CopySurface() because CopySurface |
873 | 0 | // is optimized for memcpy and therefore isn't good for format conversion. |
874 | 0 | // Using OP_OVER since in our case it's equivalent to OP_SOURCE and |
875 | 0 | // generally more optimized. |
876 | 0 | dt->DrawSurface(aSurface, bounds, bounds, DrawSurfaceOptions(), |
877 | 0 | DrawOptions(1.0f, CompositionOp::OP_OVER)); |
878 | 0 | RefPtr<SourceSurface> surface = dt->Snapshot(); |
879 | 0 | return surface->GetDataSurface(); |
880 | 0 | } |
881 | 0 | |
882 | 0 | // If the surface IS of type DATA then it may or may not be in main memory |
883 | 0 | // depending on whether or not it has been mapped yet. We have no way of |
884 | 0 | // knowing, so we can't be sure if it's best to create a data wrapping |
885 | 0 | // DrawTarget for the conversion or an offscreen content DrawTarget. We could |
886 | 0 | // guess it's not mapped and create an offscreen content DrawTarget, but if |
887 | 0 | // it is then we'll end up uploading the surface data, and most likely the |
888 | 0 | // caller is going to be accessing the resulting surface data, resulting in a |
889 | 0 | // readback (both very expensive operations). Alternatively we could guess |
890 | 0 | // the data is mapped and create a data wrapping DrawTarget and, if the |
891 | 0 | // surface is not in main memory, then we will incure a readback. The latter |
892 | 0 | // of these two "wrong choices" is the least costly (a readback, vs an |
893 | 0 | // upload and a readback), and more than likely the DATA surface that we've |
894 | 0 | // been passed actually IS in main memory anyway. For these reasons it's most |
895 | 0 | // likely best to create a data wrapping DrawTarget here to do the format |
896 | 0 | // conversion. |
897 | 0 | RefPtr<DataSourceSurface> dataSurface = |
898 | 0 | Factory::CreateDataSourceSurface(aSurface->GetSize(), aFormat); |
899 | 0 | DataSourceSurface::MappedSurface map; |
900 | 0 | if (!dataSurface || |
901 | 0 | !dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map)) { |
902 | 0 | return nullptr; |
903 | 0 | } |
904 | 0 | RefPtr<DrawTarget> dt = |
905 | 0 | Factory::CreateDrawTargetForData(BackendType::CAIRO, |
906 | 0 | map.mData, |
907 | 0 | dataSurface->GetSize(), |
908 | 0 | map.mStride, |
909 | 0 | aFormat); |
910 | 0 | if (!dt) { |
911 | 0 | dataSurface->Unmap(); |
912 | 0 | return nullptr; |
913 | 0 | } |
914 | 0 | // Using DrawSurface() here rather than CopySurface() because CopySurface |
915 | 0 | // is optimized for memcpy and therefore isn't good for format conversion. |
916 | 0 | // Using OP_OVER since in our case it's equivalent to OP_SOURCE and |
917 | 0 | // generally more optimized. |
918 | 0 | dt->DrawSurface(aSurface, bounds, bounds, DrawSurfaceOptions(), |
919 | 0 | DrawOptions(1.0f, CompositionOp::OP_OVER)); |
920 | 0 | dataSurface->Unmap(); |
921 | 0 | return dataSurface.forget(); |
922 | 0 | } |
923 | | |
924 | | const uint32_t gfxUtils::sNumFrameColors = 8; |
925 | | |
926 | | /* static */ const gfx::Color& |
927 | | gfxUtils::GetColorForFrameNumber(uint64_t aFrameNumber) |
928 | 0 | { |
929 | 0 | static bool initialized = false; |
930 | 0 | static gfx::Color colors[sNumFrameColors]; |
931 | 0 |
|
932 | 0 | if (!initialized) { |
933 | 0 | uint32_t i = 0; |
934 | 0 | colors[i++] = gfx::Color::FromABGR(0xffff0000); |
935 | 0 | colors[i++] = gfx::Color::FromABGR(0xffcc00ff); |
936 | 0 | colors[i++] = gfx::Color::FromABGR(0xff0066cc); |
937 | 0 | colors[i++] = gfx::Color::FromABGR(0xff00ff00); |
938 | 0 | colors[i++] = gfx::Color::FromABGR(0xff33ffff); |
939 | 0 | colors[i++] = gfx::Color::FromABGR(0xffff0099); |
940 | 0 | colors[i++] = gfx::Color::FromABGR(0xff0000ff); |
941 | 0 | colors[i++] = gfx::Color::FromABGR(0xff999999); |
942 | 0 | MOZ_ASSERT(i == sNumFrameColors); |
943 | 0 | initialized = true; |
944 | 0 | } |
945 | 0 |
|
946 | 0 | return colors[aFrameNumber % sNumFrameColors]; |
947 | 0 | } |
948 | | |
949 | | /* static */ nsresult |
950 | | gfxUtils::EncodeSourceSurface(SourceSurface* aSurface, |
951 | | const nsACString& aMimeType, |
952 | | const nsAString& aOutputOptions, |
953 | | BinaryOrData aBinaryOrData, |
954 | | FILE* aFile, |
955 | | nsACString* aStrOut) |
956 | 0 | { |
957 | 0 | MOZ_ASSERT(aBinaryOrData == gfxUtils::eDataURIEncode || aFile || aStrOut, |
958 | 0 | "Copying binary encoding to clipboard not currently supported"); |
959 | 0 |
|
960 | 0 | const IntSize size = aSurface->GetSize(); |
961 | 0 | if (size.IsEmpty()) { |
962 | 0 | return NS_ERROR_INVALID_ARG; |
963 | 0 | } |
964 | 0 | const Size floatSize(size.width, size.height); |
965 | 0 |
|
966 | 0 | RefPtr<DataSourceSurface> dataSurface; |
967 | 0 | if (aSurface->GetFormat() != SurfaceFormat::B8G8R8A8) { |
968 | 0 | // FIXME bug 995807 (B8G8R8X8), bug 831898 (R5G6B5) |
969 | 0 | dataSurface = |
970 | 0 | gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(aSurface, |
971 | 0 | SurfaceFormat::B8G8R8A8); |
972 | 0 | } else { |
973 | 0 | dataSurface = aSurface->GetDataSurface(); |
974 | 0 | } |
975 | 0 | if (!dataSurface) { |
976 | 0 | return NS_ERROR_FAILURE; |
977 | 0 | } |
978 | 0 | |
979 | 0 | DataSourceSurface::MappedSurface map; |
980 | 0 | if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) { |
981 | 0 | return NS_ERROR_FAILURE; |
982 | 0 | } |
983 | 0 | |
984 | 0 | nsAutoCString encoderCID( |
985 | 0 | NS_LITERAL_CSTRING("@mozilla.org/image/encoder;2?type=") + aMimeType); |
986 | 0 | nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(encoderCID.get()); |
987 | 0 | if (!encoder) { |
988 | | #ifdef DEBUG |
989 | | int32_t w = std::min(size.width, 8); |
990 | | int32_t h = std::min(size.height, 8); |
991 | | printf("Could not create encoder. Top-left %dx%d pixels contain:\n", w, h); |
992 | | for (int32_t y = 0; y < h; ++y) { |
993 | | for (int32_t x = 0; x < w; ++x) { |
994 | | printf("%x ", reinterpret_cast<uint32_t*>(map.mData)[y*map.mStride+x]); |
995 | | } |
996 | | } |
997 | | #endif |
998 | | dataSurface->Unmap(); |
999 | 0 | return NS_ERROR_FAILURE; |
1000 | 0 | } |
1001 | 0 |
|
1002 | 0 | nsresult rv = encoder->InitFromData(map.mData, |
1003 | 0 | BufferSizeFromStrideAndHeight(map.mStride, size.height), |
1004 | 0 | size.width, |
1005 | 0 | size.height, |
1006 | 0 | map.mStride, |
1007 | 0 | imgIEncoder::INPUT_FORMAT_HOSTARGB, |
1008 | 0 | aOutputOptions); |
1009 | 0 | dataSurface->Unmap(); |
1010 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1011 | 0 |
|
1012 | 0 | nsCOMPtr<nsIInputStream> imgStream; |
1013 | 0 | CallQueryInterface(encoder.get(), getter_AddRefs(imgStream)); |
1014 | 0 | if (!imgStream) { |
1015 | 0 | return NS_ERROR_FAILURE; |
1016 | 0 | } |
1017 | 0 | |
1018 | 0 | uint64_t bufSize64; |
1019 | 0 | rv = imgStream->Available(&bufSize64); |
1020 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1021 | 0 |
|
1022 | 0 | NS_ENSURE_TRUE(bufSize64 < UINT32_MAX - 16, NS_ERROR_FAILURE); |
1023 | 0 |
|
1024 | 0 | uint32_t bufSize = (uint32_t)bufSize64; |
1025 | 0 |
|
1026 | 0 | // ...leave a little extra room so we can call read again and make sure we |
1027 | 0 | // got everything. 16 bytes for better padding (maybe) |
1028 | 0 | bufSize += 16; |
1029 | 0 | uint32_t imgSize = 0; |
1030 | 0 | Vector<char> imgData; |
1031 | 0 | if (!imgData.initCapacity(bufSize)) { |
1032 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
1033 | 0 | } |
1034 | 0 | uint32_t numReadThisTime = 0; |
1035 | 0 | while ((rv = imgStream->Read(imgData.begin() + imgSize, |
1036 | 0 | bufSize - imgSize, |
1037 | 0 | &numReadThisTime)) == NS_OK && numReadThisTime > 0) |
1038 | 0 | { |
1039 | 0 | // Update the length of the vector without overwriting the new data. |
1040 | 0 | if (!imgData.growByUninitialized(numReadThisTime)) { |
1041 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
1042 | 0 | } |
1043 | 0 | |
1044 | 0 | imgSize += numReadThisTime; |
1045 | 0 | if (imgSize == bufSize) { |
1046 | 0 | // need a bigger buffer, just double |
1047 | 0 | bufSize *= 2; |
1048 | 0 | if (!imgData.resizeUninitialized(bufSize)) { |
1049 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
1050 | 0 | } |
1051 | 0 | } |
1052 | 0 | } |
1053 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1054 | 0 | NS_ENSURE_TRUE(!imgData.empty(), NS_ERROR_FAILURE); |
1055 | 0 |
|
1056 | 0 | if (aBinaryOrData == gfxUtils::eBinaryEncode) { |
1057 | 0 | if (aFile) { |
1058 | 0 | Unused << fwrite(imgData.begin(), 1, imgSize, aFile); |
1059 | 0 | } |
1060 | 0 | return NS_OK; |
1061 | 0 | } |
1062 | 0 |
|
1063 | 0 | // base 64, result will be null-terminated |
1064 | 0 | nsCString encodedImg; |
1065 | 0 | rv = Base64Encode(Substring(imgData.begin(), imgSize), encodedImg); |
1066 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1067 | 0 |
|
1068 | 0 | nsCString stringBuf; |
1069 | 0 | nsACString& string = aStrOut ? *aStrOut : stringBuf; |
1070 | 0 | string.AppendLiteral("data:"); |
1071 | 0 | string.Append(aMimeType); |
1072 | 0 | string.AppendLiteral(";base64,"); |
1073 | 0 | string.Append(encodedImg); |
1074 | 0 |
|
1075 | 0 | if (aFile) { |
1076 | | #ifdef ANDROID |
1077 | | if (aFile == stdout || aFile == stderr) { |
1078 | | // ADB logcat cuts off long strings so we will break it down |
1079 | | const char* cStr = string.BeginReading(); |
1080 | | size_t len = strlen(cStr); |
1081 | | while (true) { |
1082 | | printf_stderr("IMG: %.140s\n", cStr); |
1083 | | if (len <= 140) |
1084 | | break; |
1085 | | len -= 140; |
1086 | | cStr += 140; |
1087 | | } |
1088 | | } |
1089 | | #endif |
1090 | | fprintf(aFile, "%s", string.BeginReading()); |
1091 | 0 | } else if (!aStrOut) { |
1092 | 0 | nsCOMPtr<nsIClipboardHelper> clipboard(do_GetService("@mozilla.org/widget/clipboardhelper;1", &rv)); |
1093 | 0 | if (clipboard) { |
1094 | 0 | clipboard->CopyString(NS_ConvertASCIItoUTF16(string)); |
1095 | 0 | } |
1096 | 0 | } |
1097 | 0 | return NS_OK; |
1098 | 0 | } |
1099 | | |
1100 | | static nsCString |
1101 | | EncodeSourceSurfaceAsPNGURI(SourceSurface* aSurface) |
1102 | 0 | { |
1103 | 0 | nsCString string; |
1104 | 0 | gfxUtils::EncodeSourceSurface(aSurface, NS_LITERAL_CSTRING("image/png"), |
1105 | 0 | EmptyString(), gfxUtils::eDataURIEncode, |
1106 | 0 | nullptr, &string); |
1107 | 0 | return string; |
1108 | 0 | } |
1109 | | |
1110 | | // https://jdashg.github.io/misc/colors/from-coeffs.html |
1111 | | const float kBT601NarrowYCbCrToRGB_RowMajor[16] = { |
1112 | | 1.16438f, 0.00000f, 1.59603f,-0.87420f, |
1113 | | 1.16438f,-0.39176f,-0.81297f, 0.53167f, |
1114 | | 1.16438f, 2.01723f, 0.00000f,-1.08563f, |
1115 | | 0.00000f, 0.00000f, 0.00000f, 1.00000f |
1116 | | }; |
1117 | | const float kBT709NarrowYCbCrToRGB_RowMajor[16] = { |
1118 | | 1.16438f, 0.00000f, 1.79274f,-0.97295f, |
1119 | | 1.16438f,-0.21325f,-0.53291f, 0.30148f, |
1120 | | 1.16438f, 2.11240f, 0.00000f,-1.13340f, |
1121 | | 0.00000f, 0.00000f, 0.00000f, 1.00000f |
1122 | | }; |
1123 | | |
1124 | | /* static */ const float* |
1125 | | gfxUtils::YuvToRgbMatrix4x3RowMajor(YUVColorSpace aYUVColorSpace) |
1126 | 0 | { |
1127 | 0 | #define X(x) { x[0], x[1], x[ 2], 0.0f, \ |
1128 | 0 | x[4], x[5], x[ 6], 0.0f, \ |
1129 | 0 | x[8], x[9], x[10], 0.0f } |
1130 | 0 |
|
1131 | 0 | static const float rec601[12] = X(kBT601NarrowYCbCrToRGB_RowMajor); |
1132 | 0 | static const float rec709[12] = X(kBT709NarrowYCbCrToRGB_RowMajor); |
1133 | 0 |
|
1134 | 0 | #undef X |
1135 | 0 |
|
1136 | 0 | switch (aYUVColorSpace) { |
1137 | 0 | case YUVColorSpace::BT601: |
1138 | 0 | return rec601; |
1139 | 0 | case YUVColorSpace::BT709: |
1140 | 0 | return rec709; |
1141 | 0 | default: // YUVColorSpace::UNKNOWN |
1142 | 0 | MOZ_ASSERT(false, "unknown aYUVColorSpace"); |
1143 | 0 | return rec601; |
1144 | 0 | } |
1145 | 0 | } |
1146 | | |
1147 | | /* static */ const float* |
1148 | | gfxUtils::YuvToRgbMatrix3x3ColumnMajor(YUVColorSpace aYUVColorSpace) |
1149 | 0 | { |
1150 | 0 | #define X(x) { x[0], x[4], x[ 8], \ |
1151 | 0 | x[1], x[5], x[ 9], \ |
1152 | 0 | x[2], x[6], x[10] } |
1153 | 0 |
|
1154 | 0 | static const float rec601[9] = X(kBT601NarrowYCbCrToRGB_RowMajor); |
1155 | 0 | static const float rec709[9] = X(kBT709NarrowYCbCrToRGB_RowMajor); |
1156 | 0 |
|
1157 | 0 | #undef X |
1158 | 0 |
|
1159 | 0 | switch (aYUVColorSpace) { |
1160 | 0 | case YUVColorSpace::BT601: |
1161 | 0 | return rec601; |
1162 | 0 | case YUVColorSpace::BT709: |
1163 | 0 | return rec709; |
1164 | 0 | default: // YUVColorSpace::UNKNOWN |
1165 | 0 | MOZ_ASSERT(false, "unknown aYUVColorSpace"); |
1166 | 0 | return rec601; |
1167 | 0 | } |
1168 | 0 | } |
1169 | | |
1170 | | /* static */ const float* |
1171 | | gfxUtils::YuvToRgbMatrix4x4ColumnMajor(YUVColorSpace aYUVColorSpace) |
1172 | 0 | { |
1173 | 0 | #define X(x) { x[0], x[4], x[ 8], x[12], \ |
1174 | 0 | x[1], x[5], x[ 9], x[13], \ |
1175 | 0 | x[2], x[6], x[10], x[14], \ |
1176 | 0 | x[3], x[7], x[11], x[15] } |
1177 | 0 |
|
1178 | 0 | static const float rec601[16] = X(kBT601NarrowYCbCrToRGB_RowMajor); |
1179 | 0 | static const float rec709[16] = X(kBT709NarrowYCbCrToRGB_RowMajor); |
1180 | 0 |
|
1181 | 0 | #undef X |
1182 | 0 |
|
1183 | 0 | switch (aYUVColorSpace) { |
1184 | 0 | case YUVColorSpace::BT601: |
1185 | 0 | return rec601; |
1186 | 0 | case YUVColorSpace::BT709: |
1187 | 0 | return rec709; |
1188 | 0 | default: // YUVColorSpace::UNKNOWN |
1189 | 0 | MOZ_ASSERT(false, "unknown aYUVColorSpace"); |
1190 | 0 | return rec601; |
1191 | 0 | } |
1192 | 0 | } |
1193 | | |
1194 | | /* static */ void |
1195 | | gfxUtils::WriteAsPNG(SourceSurface* aSurface, const nsAString& aFile) |
1196 | 0 | { |
1197 | 0 | WriteAsPNG(aSurface, NS_ConvertUTF16toUTF8(aFile).get()); |
1198 | 0 | } |
1199 | | |
1200 | | /* static */ void |
1201 | | gfxUtils::WriteAsPNG(SourceSurface* aSurface, const char* aFile) |
1202 | 0 | { |
1203 | 0 | FILE* file = fopen(aFile, "wb"); |
1204 | 0 |
|
1205 | 0 | if (!file) { |
1206 | 0 | // Maybe the directory doesn't exist; try creating it, then fopen again. |
1207 | 0 | nsresult rv = NS_ERROR_FAILURE; |
1208 | 0 | nsCOMPtr<nsIFile> comFile = do_CreateInstance("@mozilla.org/file/local;1"); |
1209 | 0 | if (comFile) { |
1210 | 0 | NS_ConvertUTF8toUTF16 utf16path((nsDependentCString(aFile))); |
1211 | 0 | rv = comFile->InitWithPath(utf16path); |
1212 | 0 | if (NS_SUCCEEDED(rv)) { |
1213 | 0 | nsCOMPtr<nsIFile> dirPath; |
1214 | 0 | comFile->GetParent(getter_AddRefs(dirPath)); |
1215 | 0 | if (dirPath) { |
1216 | 0 | rv = dirPath->Create(nsIFile::DIRECTORY_TYPE, 0777); |
1217 | 0 | if (NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_ALREADY_EXISTS) { |
1218 | 0 | file = fopen(aFile, "wb"); |
1219 | 0 | } |
1220 | 0 | } |
1221 | 0 | } |
1222 | 0 | } |
1223 | 0 | if (!file) { |
1224 | 0 | NS_WARNING("Failed to open file to create PNG!"); |
1225 | 0 | return; |
1226 | 0 | } |
1227 | 0 | } |
1228 | 0 |
|
1229 | 0 | EncodeSourceSurface(aSurface, NS_LITERAL_CSTRING("image/png"), |
1230 | 0 | EmptyString(), eBinaryEncode, file); |
1231 | 0 | fclose(file); |
1232 | 0 | } |
1233 | | |
1234 | | /* static */ void |
1235 | | gfxUtils::WriteAsPNG(DrawTarget* aDT, const nsAString& aFile) |
1236 | 0 | { |
1237 | 0 | WriteAsPNG(aDT, NS_ConvertUTF16toUTF8(aFile).get()); |
1238 | 0 | } |
1239 | | |
1240 | | /* static */ void |
1241 | | gfxUtils::WriteAsPNG(DrawTarget* aDT, const char* aFile) |
1242 | 0 | { |
1243 | 0 | RefPtr<SourceSurface> surface = aDT->Snapshot(); |
1244 | 0 | if (surface) { |
1245 | 0 | WriteAsPNG(surface, aFile); |
1246 | 0 | } else { |
1247 | 0 | NS_WARNING("Failed to get surface!"); |
1248 | 0 | } |
1249 | 0 | } |
1250 | | |
1251 | | /* static */ void |
1252 | | gfxUtils::WriteAsPNG(nsIPresShell* aShell, const char* aFile) |
1253 | 0 | { |
1254 | 0 | int32_t width = 1000, height = 1000; |
1255 | 0 | nsRect r(0, 0, aShell->GetPresContext()->DevPixelsToAppUnits(width), |
1256 | 0 | aShell->GetPresContext()->DevPixelsToAppUnits(height)); |
1257 | 0 |
|
1258 | 0 | RefPtr<mozilla::gfx::DrawTarget> dt = gfxPlatform::GetPlatform()-> |
1259 | 0 | CreateOffscreenContentDrawTarget(IntSize(width, height), |
1260 | 0 | SurfaceFormat::B8G8R8A8); |
1261 | 0 | NS_ENSURE_TRUE(dt && dt->IsValid(), /*void*/); |
1262 | 0 |
|
1263 | 0 | RefPtr<gfxContext> context = gfxContext::CreateOrNull(dt); |
1264 | 0 | MOZ_ASSERT(context); // already checked the draw target above |
1265 | 0 | aShell->RenderDocument(r, 0, NS_RGB(255, 255, 0), context); |
1266 | 0 | WriteAsPNG(dt.get(), aFile); |
1267 | 0 | } |
1268 | | |
1269 | | /* static */ void |
1270 | | gfxUtils::DumpAsDataURI(SourceSurface* aSurface, FILE* aFile) |
1271 | 0 | { |
1272 | 0 | EncodeSourceSurface(aSurface, NS_LITERAL_CSTRING("image/png"), |
1273 | 0 | EmptyString(), eDataURIEncode, aFile); |
1274 | 0 | } |
1275 | | |
1276 | | /* static */ nsCString |
1277 | | gfxUtils::GetAsDataURI(SourceSurface* aSurface) |
1278 | 0 | { |
1279 | 0 | return EncodeSourceSurfaceAsPNGURI(aSurface); |
1280 | 0 | } |
1281 | | |
1282 | | /* static */ void |
1283 | | gfxUtils::DumpAsDataURI(DrawTarget* aDT, FILE* aFile) |
1284 | 0 | { |
1285 | 0 | RefPtr<SourceSurface> surface = aDT->Snapshot(); |
1286 | 0 | if (surface) { |
1287 | 0 | DumpAsDataURI(surface, aFile); |
1288 | 0 | } else { |
1289 | 0 | NS_WARNING("Failed to get surface!"); |
1290 | 0 | } |
1291 | 0 | } |
1292 | | |
1293 | | /* static */ nsCString |
1294 | | gfxUtils::GetAsLZ4Base64Str(DataSourceSurface* aSourceSurface) |
1295 | 0 | { |
1296 | 0 | DataSourceSurface::ScopedMap map(aSourceSurface, DataSourceSurface::READ); |
1297 | 0 | int32_t dataSize = aSourceSurface->GetSize().height * map.GetStride(); |
1298 | 0 | auto compressedData = MakeUnique<char[]>(LZ4::maxCompressedSize(dataSize)); |
1299 | 0 | if (compressedData) { |
1300 | 0 | int nDataSize = LZ4::compress((char*)map.GetData(), |
1301 | 0 | dataSize, |
1302 | 0 | compressedData.get()); |
1303 | 0 | if (nDataSize > 0) { |
1304 | 0 | nsCString encodedImg; |
1305 | 0 | nsresult rv = Base64Encode(Substring(compressedData.get(), nDataSize), encodedImg); |
1306 | 0 | if (rv == NS_OK) { |
1307 | 0 | nsCString string(""); |
1308 | 0 | string.AppendPrintf("data:image/lz4bgra;base64,%i,%i,%i,", |
1309 | 0 | aSourceSurface->GetSize().width, |
1310 | 0 | map.GetStride(), |
1311 | 0 | aSourceSurface->GetSize().height); |
1312 | 0 | string.Append(encodedImg); |
1313 | 0 | return string; |
1314 | 0 | } |
1315 | 0 | } |
1316 | 0 | } |
1317 | 0 | return nsCString(""); |
1318 | 0 | } |
1319 | | |
1320 | | /* static */ nsCString |
1321 | | gfxUtils::GetAsDataURI(DrawTarget* aDT) |
1322 | 0 | { |
1323 | 0 | RefPtr<SourceSurface> surface = aDT->Snapshot(); |
1324 | 0 | if (surface) { |
1325 | 0 | return EncodeSourceSurfaceAsPNGURI(surface); |
1326 | 0 | } else { |
1327 | 0 | NS_WARNING("Failed to get surface!"); |
1328 | 0 | return nsCString(""); |
1329 | 0 | } |
1330 | 0 | } |
1331 | | |
1332 | | /* static */ void |
1333 | | gfxUtils::CopyAsDataURI(SourceSurface* aSurface) |
1334 | 0 | { |
1335 | 0 | EncodeSourceSurface(aSurface, NS_LITERAL_CSTRING("image/png"), |
1336 | 0 | EmptyString(), eDataURIEncode, nullptr); |
1337 | 0 | } |
1338 | | |
1339 | | /* static */ void |
1340 | | gfxUtils::CopyAsDataURI(DrawTarget* aDT) |
1341 | 0 | { |
1342 | 0 | RefPtr<SourceSurface> surface = aDT->Snapshot(); |
1343 | 0 | if (surface) { |
1344 | 0 | CopyAsDataURI(surface); |
1345 | 0 | } else { |
1346 | 0 | NS_WARNING("Failed to get surface!"); |
1347 | 0 | } |
1348 | 0 | } |
1349 | | |
1350 | | /* static */ UniquePtr<uint8_t[]> |
1351 | | gfxUtils::GetImageBuffer(gfx::DataSourceSurface* aSurface, |
1352 | | bool aIsAlphaPremultiplied, |
1353 | | int32_t* outFormat) |
1354 | 0 | { |
1355 | 0 | *outFormat = 0; |
1356 | 0 |
|
1357 | 0 | DataSourceSurface::MappedSurface map; |
1358 | 0 | if (!aSurface->Map(DataSourceSurface::MapType::READ, &map)) |
1359 | 0 | return nullptr; |
1360 | 0 | |
1361 | 0 | uint32_t bufferSize = aSurface->GetSize().width * aSurface->GetSize().height * 4; |
1362 | 0 | auto imageBuffer = MakeUniqueFallible<uint8_t[]>(bufferSize); |
1363 | 0 | if (!imageBuffer) { |
1364 | 0 | aSurface->Unmap(); |
1365 | 0 | return nullptr; |
1366 | 0 | } |
1367 | 0 | memcpy(imageBuffer.get(), map.mData, bufferSize); |
1368 | 0 |
|
1369 | 0 | aSurface->Unmap(); |
1370 | 0 |
|
1371 | 0 | int32_t format = imgIEncoder::INPUT_FORMAT_HOSTARGB; |
1372 | 0 | if (!aIsAlphaPremultiplied) { |
1373 | 0 | // We need to convert to INPUT_FORMAT_RGBA, otherwise |
1374 | 0 | // we are automatically considered premult, and unpremult'd. |
1375 | 0 | // Yes, it is THAT silly. |
1376 | 0 | // Except for different lossy conversions by color, |
1377 | 0 | // we could probably just change the label, and not change the data. |
1378 | 0 | gfxUtils::ConvertBGRAtoRGBA(imageBuffer.get(), bufferSize); |
1379 | 0 | format = imgIEncoder::INPUT_FORMAT_RGBA; |
1380 | 0 | } |
1381 | 0 |
|
1382 | 0 | *outFormat = format; |
1383 | 0 | return imageBuffer; |
1384 | 0 | } |
1385 | | |
1386 | | /* static */ nsresult |
1387 | | gfxUtils::GetInputStream(gfx::DataSourceSurface* aSurface, |
1388 | | bool aIsAlphaPremultiplied, |
1389 | | const char* aMimeType, |
1390 | | const char16_t* aEncoderOptions, |
1391 | | nsIInputStream** outStream) |
1392 | 0 | { |
1393 | 0 | nsCString enccid("@mozilla.org/image/encoder;2?type="); |
1394 | 0 | enccid += aMimeType; |
1395 | 0 | nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get()); |
1396 | 0 | if (!encoder) |
1397 | 0 | return NS_ERROR_FAILURE; |
1398 | 0 | |
1399 | 0 | int32_t format = 0; |
1400 | 0 | UniquePtr<uint8_t[]> imageBuffer = GetImageBuffer(aSurface, aIsAlphaPremultiplied, &format); |
1401 | 0 | if (!imageBuffer) |
1402 | 0 | return NS_ERROR_FAILURE; |
1403 | 0 | |
1404 | 0 | return dom::ImageEncoder::GetInputStream(aSurface->GetSize().width, |
1405 | 0 | aSurface->GetSize().height, |
1406 | 0 | imageBuffer.get(), format, |
1407 | 0 | encoder, aEncoderOptions, outStream); |
1408 | 0 | } |
1409 | | |
1410 | | class GetFeatureStatusRunnable final : public dom::WorkerMainThreadRunnable |
1411 | | { |
1412 | | public: |
1413 | | GetFeatureStatusRunnable(dom::WorkerPrivate* workerPrivate, |
1414 | | const nsCOMPtr<nsIGfxInfo>& gfxInfo, |
1415 | | int32_t feature, |
1416 | | nsACString& failureId, |
1417 | | int32_t* status) |
1418 | | : WorkerMainThreadRunnable(workerPrivate, |
1419 | | NS_LITERAL_CSTRING("GFX :: GetFeatureStatus")) |
1420 | | , mGfxInfo(gfxInfo) |
1421 | | , mFeature(feature) |
1422 | | , mStatus(status) |
1423 | | , mFailureId(failureId) |
1424 | | , mNSResult(NS_OK) |
1425 | 0 | { |
1426 | 0 | } |
1427 | | |
1428 | | bool MainThreadRun() override |
1429 | 0 | { |
1430 | 0 | if (mGfxInfo) { |
1431 | 0 | mNSResult = mGfxInfo->GetFeatureStatus(mFeature, mFailureId, mStatus); |
1432 | 0 | } |
1433 | 0 | return true; |
1434 | 0 | } |
1435 | | |
1436 | | nsresult GetNSResult() const |
1437 | 0 | { |
1438 | 0 | return mNSResult; |
1439 | 0 | } |
1440 | | |
1441 | | protected: |
1442 | 0 | ~GetFeatureStatusRunnable() {} |
1443 | | |
1444 | | private: |
1445 | | nsCOMPtr<nsIGfxInfo> mGfxInfo; |
1446 | | int32_t mFeature; |
1447 | | int32_t* mStatus; |
1448 | | nsACString& mFailureId; |
1449 | | nsresult mNSResult; |
1450 | | }; |
1451 | | |
1452 | | /* static */ nsresult |
1453 | | gfxUtils::ThreadSafeGetFeatureStatus(const nsCOMPtr<nsIGfxInfo>& gfxInfo, |
1454 | | int32_t feature, nsACString& failureId, |
1455 | | int32_t* status) |
1456 | 0 | { |
1457 | 0 | if (!NS_IsMainThread()) { |
1458 | 0 | dom::WorkerPrivate* workerPrivate = dom::GetCurrentThreadWorkerPrivate(); |
1459 | 0 |
|
1460 | 0 | RefPtr<GetFeatureStatusRunnable> runnable = |
1461 | 0 | new GetFeatureStatusRunnable(workerPrivate, gfxInfo, feature, failureId, |
1462 | 0 | status); |
1463 | 0 |
|
1464 | 0 | ErrorResult rv; |
1465 | 0 | runnable->Dispatch(dom::WorkerStatus::Canceling, rv); |
1466 | 0 | if (rv.Failed()) { |
1467 | 0 | // XXXbz This is totally broken, since we're supposed to just abort |
1468 | 0 | // everything up the callstack but the callers basically eat the |
1469 | 0 | // exception. Ah, well. |
1470 | 0 | return rv.StealNSResult(); |
1471 | 0 | } |
1472 | 0 | |
1473 | 0 | return runnable->GetNSResult(); |
1474 | 0 | } |
1475 | 0 | |
1476 | 0 | return gfxInfo->GetFeatureStatus(feature, failureId, status); |
1477 | 0 | } |
1478 | | |
1479 | 0 | #define GFX_SHADER_CHECK_BUILD_VERSION_PREF "gfx-shader-check.build-version" |
1480 | 0 | #define GFX_SHADER_CHECK_DEVICE_ID_PREF "gfx-shader-check.device-id" |
1481 | 0 | #define GFX_SHADER_CHECK_DRIVER_VERSION_PREF "gfx-shader-check.driver-version" |
1482 | | |
1483 | | /* static */ void |
1484 | | gfxUtils::RemoveShaderCacheFromDiskIfNecessary() |
1485 | 0 | { |
1486 | 0 | if (!gfxVars::UseWebRenderProgramBinaryDisk()) { |
1487 | 0 | return; |
1488 | 0 | } |
1489 | 0 | |
1490 | 0 | nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo(); |
1491 | 0 |
|
1492 | 0 | // Get current values |
1493 | 0 | nsCString buildID(mozilla::PlatformBuildID()); |
1494 | 0 | nsString deviceID, driverVersion; |
1495 | 0 | gfxInfo->GetAdapterDeviceID(deviceID); |
1496 | 0 | gfxInfo->GetAdapterDriverVersion(driverVersion); |
1497 | 0 |
|
1498 | 0 | // Get pref stored values |
1499 | 0 | nsAutoCString buildIDChecked; |
1500 | 0 | Preferences::GetCString(GFX_SHADER_CHECK_BUILD_VERSION_PREF, buildIDChecked); |
1501 | 0 | nsAutoString deviceIDChecked, driverVersionChecked; |
1502 | 0 | Preferences::GetString(GFX_SHADER_CHECK_DEVICE_ID_PREF, deviceIDChecked); |
1503 | 0 | Preferences::GetString(GFX_SHADER_CHECK_DRIVER_VERSION_PREF, driverVersionChecked); |
1504 | 0 |
|
1505 | 0 | if (buildID == buildIDChecked && |
1506 | 0 | deviceID == deviceIDChecked && |
1507 | 0 | driverVersion == driverVersionChecked) { |
1508 | 0 | return; |
1509 | 0 | } |
1510 | 0 | |
1511 | 0 | nsAutoString path(gfx::gfxVars::ProfDirectory()); |
1512 | 0 |
|
1513 | 0 | if (!wr::remove_program_binary_disk_cache(&path)) { |
1514 | 0 | // Failed to remove program binary disk cache. The disk cache might have |
1515 | 0 | // invalid data. Disable program binary disk cache usage. |
1516 | 0 | gfxVars::SetUseWebRenderProgramBinaryDisk(false); |
1517 | 0 | return; |
1518 | 0 | } |
1519 | 0 | |
1520 | 0 | Preferences::SetCString(GFX_SHADER_CHECK_BUILD_VERSION_PREF, buildID); |
1521 | 0 | Preferences::SetString(GFX_SHADER_CHECK_DEVICE_ID_PREF, deviceID); |
1522 | 0 | Preferences::SetString(GFX_SHADER_CHECK_DRIVER_VERSION_PREF, driverVersion); |
1523 | 0 | return; |
1524 | 0 | } |
1525 | | |
1526 | | |
1527 | | /* static */ bool |
1528 | 0 | gfxUtils::DumpDisplayList() { |
1529 | 0 | return gfxPrefs::LayoutDumpDisplayList() || |
1530 | 0 | (gfxPrefs::LayoutDumpDisplayListParent() && XRE_IsParentProcess()) || |
1531 | 0 | (gfxPrefs::LayoutDumpDisplayListContent() && XRE_IsContentProcess()); |
1532 | 0 | } |
1533 | | |
1534 | | FILE *gfxUtils::sDumpPaintFile = stderr; |
1535 | | |
1536 | | namespace mozilla { |
1537 | | namespace gfx { |
1538 | | |
1539 | | Color ToDeviceColor(Color aColor) |
1540 | 0 | { |
1541 | 0 | // aColor is pass-by-value since to get return value optimization goodness we |
1542 | 0 | // need to return the same object from all return points in this function. We |
1543 | 0 | // could declare a local Color variable and use that, but we might as well |
1544 | 0 | // just use aColor. |
1545 | 0 | if (gfxPlatform::GetCMSMode() == eCMSMode_All) { |
1546 | 0 | qcms_transform *transform = gfxPlatform::GetCMSRGBTransform(); |
1547 | 0 | if (transform) { |
1548 | 0 | gfxPlatform::TransformPixel(aColor, aColor, transform); |
1549 | 0 | // Use the original alpha to avoid unnecessary float->byte->float |
1550 | 0 | // conversion errors |
1551 | 0 | } |
1552 | 0 | } |
1553 | 0 | return aColor; |
1554 | 0 | } |
1555 | | |
1556 | | Color ToDeviceColor(nscolor aColor) |
1557 | 0 | { |
1558 | 0 | return ToDeviceColor(Color::FromABGR(aColor)); |
1559 | 0 | } |
1560 | | |
1561 | | } // namespace gfx |
1562 | | } // namespace mozilla |