1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is thebes gfx
17 * The Initial Developer of the Original Code is
19 * Portions created by the Initial Developer are Copyright (C) 2005
20 * the Initial Developer. All Rights Reserved.
23 * Vladimir Vukicevic <vladimir@pobox.com>
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 #include "nsThebesImage.h"
40 #include "nsThebesRenderingContext.h"
42 #include "gfxContext.h"
43 #include "gfxPattern.h"
45 #include "gfxPlatform.h"
49 static PRBool gDisableOptimize
= PR_FALSE
;
52 static PRUint32 gTotalDDBs
= 0;
53 static PRUint32 gTotalDDBSize
= 0;
54 // only use up a maximum of 64MB in DDBs
55 #define kMaxDDBSize (64*1024*1024)
56 // and don't let anything in that's bigger than 4MB
57 #define kMaxSingleDDBSize (4*1024*1024)
60 NS_IMPL_ISUPPORTS1(nsThebesImage
, nsIImage
)
62 nsThebesImage::nsThebesImage()
63 : mFormat(gfxImageSurface::ImageFormatRGB24
),
67 mImageComplete(PR_FALSE
),
68 mSinglePixel(PR_FALSE
),
69 mFormatChanged(PR_FALSE
),
72 static PRBool hasCheckedOptimize
= PR_FALSE
;
73 if (!hasCheckedOptimize
) {
74 if (PR_GetEnv("MOZ_DISABLE_IMAGE_OPTIMIZE")) {
75 gDisableOptimize
= PR_TRUE
;
77 hasCheckedOptimize
= PR_TRUE
;
81 mIsDDBSurface
= PR_FALSE
;
86 nsThebesImage::Init(PRInt32 aWidth
, PRInt32 aHeight
, PRInt32 aDepth
, nsMaskRequirements aMaskRequirements
)
91 // Reject over-wide or over-tall images.
92 if (!AllowedImageSize(aWidth
, aHeight
))
93 return NS_ERROR_FAILURE
;
95 // Check to see if we are running OOM
96 nsCOMPtr
<nsIMemory
> mem
;
97 NS_GetMemoryManager(getter_AddRefs(mem
));
99 return NS_ERROR_UNEXPECTED
;
102 mem
->IsLowMemory(&lowMemory
);
104 return NS_ERROR_OUT_OF_MEMORY
;
106 gfxImageSurface::gfxImageFormat format
;
107 switch(aMaskRequirements
)
109 case nsMaskRequirements_kNeeds1Bit
:
110 format
= gfxImageSurface::ImageFormatARGB32
;
113 case nsMaskRequirements_kNeeds8Bit
:
114 format
= gfxImageSurface::ImageFormatARGB32
;
118 format
= gfxImageSurface::ImageFormatRGB24
;
126 if (!ShouldUseImageSurfaces()) {
127 mWinSurface
= new gfxWindowsSurface(gfxIntSize(mWidth
, mHeight
), format
);
128 if (mWinSurface
&& mWinSurface
->CairoStatus() == 0) {
130 mImageSurface
= mWinSurface
->GetImageSurface();
135 mWinSurface
= nsnull
;
139 mImageSurface
= new gfxImageSurface(gfxIntSize(mWidth
, mHeight
), format
);
141 if (!mImageSurface
|| mImageSurface
->CairoStatus()) {
142 mImageSurface
= nsnull
;
144 return NS_ERROR_OUT_OF_MEMORY
;
148 mQuartzSurface
= new gfxQuartzImageSurface(mImageSurface
);
151 mStride
= mImageSurface
->Stride();
156 nsThebesImage::~nsThebesImage()
161 gTotalDDBSize
-= mWidth
*mHeight
*4;
167 nsThebesImage::GetBytesPix()
173 nsThebesImage::GetIsRowOrderTopToBottom()
179 nsThebesImage::GetWidth()
185 nsThebesImage::GetHeight()
191 nsThebesImage::GetBits()
194 return mImageSurface
->Data();
199 nsThebesImage::GetLineStride()
205 nsThebesImage::GetHasAlphaMask()
207 return mAlphaDepth
> 0;
211 nsThebesImage::GetAlphaBits()
217 nsThebesImage::GetAlphaLineStride()
219 return (mAlphaDepth
> 0) ? mStride
: 0;
223 nsThebesImage::ImageUpdated(nsIDeviceContext
*aContext
, PRUint8 aFlags
, nsRect
*aUpdateRect
)
225 // Check to see if we are running OOM
226 nsCOMPtr
<nsIMemory
> mem
;
227 NS_GetMemoryManager(getter_AddRefs(mem
));
229 return NS_ERROR_UNEXPECTED
;
232 mem
->IsLowMemory(&lowMemory
);
234 return NS_ERROR_OUT_OF_MEMORY
;
236 mDecoded
.UnionRect(mDecoded
, *aUpdateRect
);
239 mQuartzSurface
->Flush();
245 nsThebesImage::GetIsImageComplete()
248 mImageComplete
= (mDecoded
== nsRect(0, 0, mWidth
, mHeight
));
249 return mImageComplete
;
253 nsThebesImage::Optimize(nsIDeviceContext
* aContext
)
255 if (gDisableOptimize
)
258 if (mOptSurface
|| mSinglePixel
)
261 /* Figure out if the entire image is a constant color */
263 // this should always be true
264 if (mStride
== mWidth
* 4) {
265 PRUint32
*imgData
= (PRUint32
*) mImageSurface
->Data();
266 PRUint32 firstPixel
= * (PRUint32
*) imgData
;
267 PRUint32 pixelCount
= mWidth
* mHeight
+ 1;
269 while (--pixelCount
&& *imgData
++ == firstPixel
)
272 if (pixelCount
== 0) {
273 // all pixels were the same
274 if (mFormat
== gfxImageSurface::ImageFormatARGB32
||
275 mFormat
== gfxImageSurface::ImageFormatRGB24
)
277 mSinglePixelColor
= gfxRGBA
279 (mFormat
== gfxImageSurface::ImageFormatRGB24
?
280 gfxRGBA::PACKED_XRGB
:
281 gfxRGBA::PACKED_ARGB_PREMULTIPLIED
));
283 mSinglePixel
= PR_TRUE
;
285 // blow away the older surfaces, to release data
287 mImageSurface
= nsnull
;
288 mOptSurface
= nsnull
;
290 mWinSurface
= nsnull
;
293 mQuartzSurface
= nsnull
;
299 // if it's not RGB24/ARGB32, don't optimize, but we never hit this at the moment
302 // if we're being forced to use image surfaces due to
303 // resource constraints, don't try to optimize beyond same-pixel.
304 if (ShouldUseImageSurfaces())
307 mOptSurface
= nsnull
;
310 // we need to special-case windows here, because windows has
311 // a distinction between DIB and DDB and we want to use DDBs as much
314 // Don't do DDBs for large images; see bug 359147
315 // Note that we bother with DDBs at all because they are much faster
316 // on some systems; on others there isn't much of a speed difference
317 // between DIBs and DDBs.
319 // Originally this just limited to 1024x1024; but that still
320 // had us hitting overall total memory usage limits (which was
321 // around 220MB on my intel shared memory system with 2GB RAM
322 // and 16-128mb in use by the video card, so I can't make
323 // heads or tails out of this limit).
325 // So instead, we clamp the max size to 64MB (this limit shuld
326 // be made dynamic based on.. something.. as soon a we figure
327 // out that something) and also limit each individual image to
328 // be less than 4MB to keep very large images out of DDBs.
330 // assume (almost -- we don't quadword-align) worst-case size
331 PRUint32 ddbSize
= mWidth
* mHeight
* 4;
332 if (ddbSize
<= kMaxSingleDDBSize
&&
333 ddbSize
+ gTotalDDBSize
<= kMaxDDBSize
)
335 nsRefPtr
<gfxWindowsSurface
> wsurf
= mWinSurface
->OptimizeToDDB(nsnull
, gfxIntSize(mWidth
, mHeight
), mFormat
);
338 gTotalDDBSize
+= ddbSize
;
339 mIsDDBSurface
= PR_TRUE
;
343 if (!mOptSurface
&& !mFormatChanged
) {
344 // just use the DIB if the format has not changed
345 mOptSurface
= mWinSurface
;
351 if (mQuartzSurface
) {
352 mQuartzSurface
->Flush();
353 mOptSurface
= mQuartzSurface
;
357 if (mOptSurface
== nsnull
)
358 mOptSurface
= gfxPlatform::GetPlatform()->OptimizeImage(mImageSurface
, mFormat
);
361 mImageSurface
= nsnull
;
363 mWinSurface
= nsnull
;
366 mQuartzSurface
= nsnull
;
374 nsThebesImage::GetColorMap()
380 nsThebesImage::GetAlphaDepth()
386 nsThebesImage::GetBitInfo()
392 nsThebesImage::LockImagePixels(PRBool aMaskPixels
)
395 return NS_ERROR_NOT_IMPLEMENTED
;
396 if ((mOptSurface
|| mSinglePixel
) && !mImageSurface
) {
397 // Recover the pixels
398 mImageSurface
= new gfxImageSurface(gfxIntSize(mWidth
, mHeight
),
399 gfxImageSurface::ImageFormatARGB32
);
400 if (!mImageSurface
|| mImageSurface
->CairoStatus())
401 return NS_ERROR_OUT_OF_MEMORY
;
402 gfxContext
context(mImageSurface
);
403 context
.SetOperator(gfxContext::OPERATOR_SOURCE
);
405 context
.SetDeviceColor(mSinglePixelColor
);
407 context
.SetSource(mOptSurface
);
411 mWinSurface
= nsnull
;
414 mQuartzSurface
= nsnull
;
422 nsThebesImage::UnlockImagePixels(PRBool aMaskPixels
)
425 return NS_ERROR_NOT_IMPLEMENTED
;
426 mOptSurface
= nsnull
;
429 mQuartzSurface
->Flush();
435 IsSafeImageTransformComponent(gfxFloat aValue
)
437 return aValue
>= -32768 && aValue
<= 32767;
441 nsThebesImage::Draw(gfxContext
* aContext
,
442 const gfxMatrix
& aUserSpaceToImageSpace
,
443 const gfxRect
& aFill
,
444 const nsIntMargin
& aPadding
,
445 const nsIntRect
& aSubimage
)
447 NS_ASSERTION(!aFill
.IsEmpty(), "zero dest size --- fix caller");
448 NS_ASSERTION(!aSubimage
.IsEmpty(), "zero source size --- fix caller");
450 PRBool doPadding
= aPadding
!= nsIntMargin(0,0,0,0);
451 PRBool doPartialDecode
= !GetIsImageComplete();
452 gfxContext::GraphicsOperator op
= aContext
->CurrentOperator();
454 if (mSinglePixel
&& !doPadding
&& !doPartialDecode
) {
455 // Single-color fast path
456 // if a == 0, it's a noop
457 if (mSinglePixelColor
.a
== 0.0)
460 if (op
== gfxContext::OPERATOR_OVER
&& mSinglePixelColor
.a
== 1.0)
461 aContext
->SetOperator(gfxContext::OPERATOR_SOURCE
);
463 aContext
->SetDeviceColor(mSinglePixelColor
);
465 aContext
->Rectangle(aFill
);
467 aContext
->SetOperator(op
);
468 aContext
->SetDeviceColor(gfxRGBA(0,0,0,0));
472 gfxMatrix userSpaceToImageSpace
= aUserSpaceToImageSpace
;
473 gfxRect sourceRect
= userSpaceToImageSpace
.Transform(aFill
);
474 gfxRect
imageRect(0, 0, mWidth
+ aPadding
.LeftRight(), mHeight
+ aPadding
.TopBottom());
475 gfxRect
subimage(aSubimage
.x
, aSubimage
.y
, aSubimage
.width
, aSubimage
.height
);
476 gfxRect fill
= aFill
;
477 nsRefPtr
<gfxASurface
> surface
;
478 gfxImageSurface::gfxImageFormat format
;
480 NS_ASSERTION(!sourceRect
.Intersect(subimage
).IsEmpty(),
481 "We must be allowed to sample *some* source pixels!");
483 PRBool doTile
= !imageRect
.Contains(sourceRect
);
484 if (doPadding
|| doPartialDecode
) {
485 gfxRect available
= gfxRect(mDecoded
.x
, mDecoded
.y
, mDecoded
.width
, mDecoded
.height
) +
486 gfxPoint(aPadding
.left
, aPadding
.top
);
488 if (!doTile
&& !mSinglePixel
) {
489 // Not tiling, and we have a surface, so we can account for
490 // padding and/or a partial decode just by twiddling parameters.
491 // First, update our user-space fill rect.
492 sourceRect
= sourceRect
.Intersect(available
);
493 gfxMatrix imageSpaceToUserSpace
= userSpaceToImageSpace
;
494 imageSpaceToUserSpace
.Invert();
495 fill
= imageSpaceToUserSpace
.Transform(sourceRect
);
497 surface
= ThebesSurface();
499 subimage
= subimage
.Intersect(available
) - gfxPoint(aPadding
.left
, aPadding
.top
);
500 userSpaceToImageSpace
.Multiply(
501 gfxMatrix().Translate(-gfxPoint(aPadding
.left
, aPadding
.top
)));
502 sourceRect
= sourceRect
- gfxPoint(aPadding
.left
, aPadding
.top
);
503 imageRect
= gfxRect(0, 0, mWidth
, mHeight
);
505 // Create a temporary surface
506 gfxIntSize
size(PRInt32(imageRect
.Width()),
507 PRInt32(imageRect
.Height()));
508 // Give this surface an alpha channel because there are
509 // transparent pixels in the padding or undecoded area
510 format
= gfxASurface::ImageFormatARGB32
;
511 surface
= gfxPlatform::GetPlatform()->CreateOffscreenSurface(size
,
513 if (!surface
|| surface
->CairoStatus() != 0)
516 // Fill 'available' with whatever we've got
517 gfxContext
tmpCtx(surface
);
518 tmpCtx
.SetOperator(gfxContext::OPERATOR_SOURCE
);
520 tmpCtx
.SetDeviceColor(mSinglePixelColor
);
522 tmpCtx
.SetSource(ThebesSurface(), gfxPoint(aPadding
.left
, aPadding
.top
));
524 tmpCtx
.Rectangle(available
);
528 NS_ASSERTION(!mSinglePixel
, "This should already have been handled");
529 surface
= ThebesSurface();
532 // At this point, we've taken care of mSinglePixel images, images with
533 // aPadding, and partially-decoded images.
535 if (!AllowedImageSize(fill
.size
.width
+ 1, fill
.size
.height
+ 1)) {
536 NS_WARNING("Destination area too large, bailing out");
540 // BEGIN working around cairo/pixman bug (bug 364968)
541 // Compute device-space-to-image-space transform. We need to sanity-
542 // check it to work around a pixman bug :-(
543 // XXX should we only do this for certain surface types?
544 gfxFloat deviceX
, deviceY
;
545 nsRefPtr
<gfxASurface
> currentTarget
=
546 aContext
->CurrentSurface(&deviceX
, &deviceY
);
547 gfxMatrix currentMatrix
= aContext
->CurrentMatrix();
548 gfxMatrix deviceToUser
= currentMatrix
;
549 deviceToUser
.Invert();
550 deviceToUser
.Translate(-gfxPoint(-deviceX
, -deviceY
));
551 gfxMatrix deviceToImage
= deviceToUser
;
552 deviceToImage
.Multiply(userSpaceToImageSpace
);
554 // Our device-space-to-image-space transform may not be acceptable to pixman.
555 if (!IsSafeImageTransformComponent(deviceToImage
.xx
) ||
556 !IsSafeImageTransformComponent(deviceToImage
.xy
) ||
557 !IsSafeImageTransformComponent(deviceToImage
.yx
) ||
558 !IsSafeImageTransformComponent(deviceToImage
.yy
)) {
559 NS_WARNING("Scaling up too much, bailing out");
563 PRBool pushedGroup
= PR_FALSE
;
564 if (!IsSafeImageTransformComponent(deviceToImage
.x0
) ||
565 !IsSafeImageTransformComponent(deviceToImage
.y0
)) {
566 // We'll push a group, which will hopefully reduce our transform's
567 // translation so it's in bounds
570 // Clip the rounded-out-to-device-pixels bounds of the
571 // transformed fill area. This is the area for the group we
573 aContext
->IdentityMatrix();
574 gfxRect bounds
= currentMatrix
.TransformBounds(fill
);
576 aContext
->Clip(bounds
);
577 aContext
->SetMatrix(currentMatrix
);
579 aContext
->PushGroup(gfxASurface::CONTENT_COLOR_ALPHA
);
580 aContext
->SetOperator(gfxContext::OPERATOR_OVER
);
581 pushedGroup
= PR_TRUE
;
583 // END working around cairo/pixman bug (bug 364968)
585 nsRefPtr
<gfxPattern
> pattern
= new gfxPattern(surface
);
586 pattern
->SetMatrix(userSpaceToImageSpace
);
588 // OK now, the hard part left is to account for the subimage sampling
589 // restriction. If all the transforms involved are just integer
590 // translations, then we assume no resampling will occur so there's
592 // XXX if only we had source-clipping in cairo!
593 if (!currentMatrix
.HasNonIntegerTranslation() &&
594 !userSpaceToImageSpace
.HasNonIntegerTranslation()) {
596 pattern
->SetExtend(gfxPattern::EXTEND_REPEAT
);
599 if (doTile
|| !subimage
.Contains(imageRect
)) {
600 // EXTEND_PAD won't help us here; we have to create a temporary
601 // surface to hold the subimage of pixels we're allowed to
603 gfxRect needed
= subimage
.Intersect(sourceRect
);
605 gfxIntSize
size(PRInt32(needed
.Width()), PRInt32(needed
.Height()));
606 NS_ASSERTION(size
.width
> 0 && size
.height
> 0,
607 "We must have some needed pixels, otherwise we don't know what to sample");
608 nsRefPtr
<gfxASurface
> temp
=
609 gfxPlatform::GetPlatform()->CreateOffscreenSurface(size
, format
);
610 if (temp
&& temp
->CairoStatus() == 0) {
611 gfxContext
tmpCtx(temp
);
612 tmpCtx
.SetOperator(gfxContext::OPERATOR_SOURCE
);
613 nsRefPtr
<gfxPattern
> tmpPattern
= new gfxPattern(surface
);
615 tmpPattern
->SetExtend(gfxPattern::EXTEND_REPEAT
);
616 tmpPattern
->SetMatrix(gfxMatrix().Translate(needed
.pos
));
617 tmpCtx
.SetPattern(tmpPattern
);
619 tmpPattern
= new gfxPattern(temp
);
621 pattern
.swap(tmpPattern
);
623 gfxMatrix(userSpaceToImageSpace
).Multiply(gfxMatrix().Translate(-needed
.pos
)));
629 // In theory we can handle this using cairo's EXTEND_PAD,
630 // but implementation limitations mean we have to consult
632 switch (currentTarget
->GetType()) {
633 case gfxASurface::SurfaceTypeXlib
:
634 case gfxASurface::SurfaceTypeXcb
: {
635 // See bug 324698. This is a workaround for EXTEND_PAD not being
636 // implemented correctly on linux in the X server.
638 // Set the filter to CAIRO_FILTER_FAST --- otherwise,
639 // pixman's sampling will sample transparency for the outside edges and we'll
640 // get blurry edges. CAIRO_EXTEND_PAD would also work here, if
643 // But don't do this for simple downscales because it's horrible.
644 // Downscaling means that device-space coordinates are
645 // scaled *up* to find the image pixel coordinates.
647 // deviceToImage is slightly stale because up above we may
648 // have adjusted the pattern's matrix ... but the adjustment
649 // is only a translation so the scale factors in deviceToImage
652 deviceToImage
.xx
>= 1.0 && deviceToImage
.yy
>= 1.0 &&
653 deviceToImage
.xy
== 0.0 && deviceToImage
.yx
== 0.0;
655 pattern
->SetFilter(0);
660 case gfxASurface::SurfaceTypeQuartz
:
661 case gfxASurface::SurfaceTypeQuartzImage
:
662 // Do nothing, Mac seems to be OK. Really?
666 // turn on EXTEND_PAD.
667 // This is what we really want for all surface types, if the
668 // implementation was universally good.
669 pattern
->SetExtend(gfxPattern::EXTEND_PAD
);
674 if ((op
== gfxContext::OPERATOR_OVER
|| pushedGroup
) &&
675 format
== gfxASurface::ImageFormatRGB24
) {
676 aContext
->SetOperator(gfxContext::OPERATOR_SOURCE
);
679 // Phew! Now we can actually draw this image
681 aContext
->SetPattern(pattern
);
682 aContext
->Rectangle(fill
);
685 aContext
->SetOperator(op
);
687 aContext
->PopGroupToSource();
694 nsThebesImage::ShouldUseImageSurfaces()
697 // There is no test on windows mobile to check for Gui resources.
698 // Allocate, until we run out of memory.
701 #elif defined(XP_WIN)
702 static const DWORD kGDIObjectsHighWaterMark
= 7000;
704 // at 7000 GDI objects, stop allocating normal images to make sure
705 // we never hit the 10k hard limit.
706 // GetCurrentProcess() just returns (HANDLE)-1, it's inlined afaik
707 DWORD count
= GetGuiResources(GetCurrentProcess(), GR_GDIOBJECTS
);
709 count
> kGDIObjectsHighWaterMark
)
711 // either something's broken (count == 0),
712 // or we hit our high water mark; disable
713 // image allocations for a bit.
721 // A hint from the image decoders that this image has no alpha, even
722 // though we created is ARGB32. This changes our format to RGB24,
723 // which in turn will cause us to Optimize() to RGB24. Has no effect
724 // after Optimize() is called, though in all cases it will be just a
725 // performance win -- the pixels are still correct and have the A byte
728 nsThebesImage::SetHasNoAlpha()
730 if (mFormat
== gfxASurface::ImageFormatARGB32
) {
731 mFormat
= gfxASurface::ImageFormatRGB24
;
732 mFormatChanged
= PR_TRUE
;