1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  *
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 "WindowSurfaceX11Image.h"
8 
9 #include "mozilla/gfx/2D.h"
10 #include "mozilla/gfx/Tools.h"
11 #include "mozilla/gfx/gfxVars.h"
12 #include "gfxPlatform.h"
13 #include "gfx2DGlue.h"
14 
15 #include <X11/extensions/shape.h>
16 
17 namespace mozilla {
18 namespace widget {
19 
20 using namespace mozilla::gfx;
21 
22 // gfxImageSurface pixel format configuration.
23 #define SHAPED_IMAGE_SURFACE_BPP 4
24 #ifdef IS_BIG_ENDIAN
25 #  define SHAPED_IMAGE_SURFACE_ALPHA_INDEX 0
26 #else
27 #  define SHAPED_IMAGE_SURFACE_ALPHA_INDEX 3
28 #endif
29 
WindowSurfaceX11Image(Display * aDisplay,Window aWindow,Visual * aVisual,unsigned int aDepth,bool aIsShaped)30 WindowSurfaceX11Image::WindowSurfaceX11Image(Display* aDisplay, Window aWindow,
31                                              Visual* aVisual,
32                                              unsigned int aDepth,
33                                              bool aIsShaped)
34     : WindowSurfaceX11(aDisplay, aWindow, aVisual, aDepth),
35       mTransparencyBitmap(nullptr),
36       mTransparencyBitmapWidth(0),
37       mTransparencyBitmapHeight(0),
38       mIsShaped(aIsShaped) {}
39 
~WindowSurfaceX11Image()40 WindowSurfaceX11Image::~WindowSurfaceX11Image() {
41   if (mTransparencyBitmap) {
42     delete[] mTransparencyBitmap;
43 
44     Display* xDisplay = mWindowSurface->XDisplay();
45     Window xDrawable = mWindowSurface->XDrawable();
46 
47     XShapeCombineMask(xDisplay, xDrawable, ShapeBounding, 0, 0, X11None,
48                       ShapeSet);
49   }
50 }
51 
Lock(const LayoutDeviceIntRegion & aRegion)52 already_AddRefed<gfx::DrawTarget> WindowSurfaceX11Image::Lock(
53     const LayoutDeviceIntRegion& aRegion) {
54   gfx::IntRect bounds = aRegion.GetBounds().ToUnknownRect();
55   gfx::IntSize size(bounds.XMost(), bounds.YMost());
56 
57   if (!mWindowSurface || mWindowSurface->CairoStatus() ||
58       !(size <= mWindowSurface->GetSize())) {
59     mWindowSurface = new gfxXlibSurface(mDisplay, mWindow, mVisual, size);
60   }
61   if (mWindowSurface->CairoStatus()) {
62     return nullptr;
63   }
64 
65   if (!mImageSurface || mImageSurface->CairoStatus() ||
66       !(size <= mImageSurface->GetSize())) {
67     gfxImageFormat format = SurfaceFormatToImageFormat(mFormat);
68     if (format == gfx::SurfaceFormat::UNKNOWN) {
69       format = mDepth == 32 ? gfx::SurfaceFormat::A8R8G8B8_UINT32
70                             : gfx::SurfaceFormat::X8R8G8B8_UINT32;
71     }
72 
73     // Use alpha image format for shaped window as we derive
74     // the shape bitmap from alpha channel. Must match SHAPED_IMAGE_SURFACE_BPP
75     // and SHAPED_IMAGE_SURFACE_ALPHA_INDEX.
76     if (mIsShaped) {
77       format = gfx::SurfaceFormat::A8R8G8B8_UINT32;
78     }
79 
80     mImageSurface = new gfxImageSurface(size, format);
81     if (mImageSurface->CairoStatus()) {
82       return nullptr;
83     }
84   }
85 
86   gfxImageFormat format = mImageSurface->Format();
87   // Cairo prefers compositing to BGRX instead of BGRA where possible.
88   // Cairo/pixman lacks some fast paths for compositing BGRX onto BGRA, so
89   // just report it as BGRX directly in that case.
90   // Otherwise, for Skia, report it as BGRA to the compositor. The alpha
91   // channel will be discarded when we put the image.
92   if (format == gfx::SurfaceFormat::X8R8G8B8_UINT32) {
93     gfx::BackendType backend = gfxVars::ContentBackend();
94     if (!gfx::Factory::DoesBackendSupportDataDrawtarget(backend)) {
95 #ifdef USE_SKIA
96       backend = gfx::BackendType::SKIA;
97 #else
98       backend = gfx::BackendType::CAIRO;
99 #endif
100     }
101     if (backend != gfx::BackendType::CAIRO) {
102       format = gfx::SurfaceFormat::A8R8G8B8_UINT32;
103     }
104   }
105 
106   return gfxPlatform::CreateDrawTargetForData(
107       mImageSurface->Data(), mImageSurface->GetSize(), mImageSurface->Stride(),
108       ImageFormatToSurfaceFormat(format));
109 }
110 
111 // The transparency bitmap routines are derived form the ones at nsWindow.cpp.
112 // The difference here is that we compose to RGBA image and then create
113 // the shape mask from final image alpha channel.
GetBitmapStride(int32_t width)114 static inline int32_t GetBitmapStride(int32_t width) { return (width + 7) / 8; }
115 
ChangedMaskBits(gchar * aMaskBits,int32_t aMaskWidth,int32_t aMaskHeight,const nsIntRect & aRect,uint8_t * aImageData)116 static bool ChangedMaskBits(gchar* aMaskBits, int32_t aMaskWidth,
117                             int32_t aMaskHeight, const nsIntRect& aRect,
118                             uint8_t* aImageData) {
119   int32_t stride = aMaskWidth * SHAPED_IMAGE_SURFACE_BPP;
120   int32_t x, y, xMax = aRect.XMost(), yMax = aRect.YMost();
121   int32_t maskBytesPerRow = GetBitmapStride(aMaskWidth);
122   for (y = aRect.y; y < yMax; y++) {
123     gchar* maskBytes = aMaskBits + y * maskBytesPerRow;
124     uint8_t* alphas = aImageData;
125     for (x = aRect.x; x < xMax; x++) {
126       bool newBit = *(alphas + SHAPED_IMAGE_SURFACE_ALPHA_INDEX) > 0x7f;
127       alphas += SHAPED_IMAGE_SURFACE_BPP;
128 
129       gchar maskByte = maskBytes[x >> 3];
130       bool maskBit = (maskByte & (1 << (x & 7))) != 0;
131 
132       if (maskBit != newBit) {
133         return true;
134       }
135     }
136     aImageData += stride;
137   }
138 
139   return false;
140 }
141 
UpdateMaskBits(gchar * aMaskBits,int32_t aMaskWidth,int32_t aMaskHeight,const nsIntRect & aRect,uint8_t * aImageData)142 static void UpdateMaskBits(gchar* aMaskBits, int32_t aMaskWidth,
143                            int32_t aMaskHeight, const nsIntRect& aRect,
144                            uint8_t* aImageData) {
145   int32_t stride = aMaskWidth * SHAPED_IMAGE_SURFACE_BPP;
146   int32_t x, y, xMax = aRect.XMost(), yMax = aRect.YMost();
147   int32_t maskBytesPerRow = GetBitmapStride(aMaskWidth);
148   for (y = aRect.y; y < yMax; y++) {
149     gchar* maskBytes = aMaskBits + y * maskBytesPerRow;
150     uint8_t* alphas = aImageData;
151     for (x = aRect.x; x < xMax; x++) {
152       bool newBit = *(alphas + SHAPED_IMAGE_SURFACE_ALPHA_INDEX) > 0x7f;
153       alphas += SHAPED_IMAGE_SURFACE_BPP;
154 
155       gchar mask = 1 << (x & 7);
156       gchar maskByte = maskBytes[x >> 3];
157       // Note: '-newBit' turns 0 into 00...00 and 1 into 11...11
158       maskBytes[x >> 3] = (maskByte & ~mask) | (-newBit & mask);
159     }
160     aImageData += stride;
161   }
162 }
163 
ResizeTransparencyBitmap(int aWidth,int aHeight)164 void WindowSurfaceX11Image::ResizeTransparencyBitmap(int aWidth, int aHeight) {
165   int32_t actualSize =
166       GetBitmapStride(mTransparencyBitmapWidth) * mTransparencyBitmapHeight;
167   int32_t newSize = GetBitmapStride(aWidth) * aHeight;
168 
169   if (actualSize < newSize) {
170     delete[] mTransparencyBitmap;
171     mTransparencyBitmap = new gchar[newSize];
172   }
173 
174   mTransparencyBitmapWidth = aWidth;
175   mTransparencyBitmapHeight = aHeight;
176 }
177 
ApplyTransparencyBitmap()178 void WindowSurfaceX11Image::ApplyTransparencyBitmap() {
179   gfx::IntSize size = mWindowSurface->GetSize();
180   bool maskChanged = true;
181 
182   if (!mTransparencyBitmap) {
183     mTransparencyBitmapWidth = size.width;
184     mTransparencyBitmapHeight = size.height;
185 
186     int32_t byteSize =
187         GetBitmapStride(mTransparencyBitmapWidth) * mTransparencyBitmapHeight;
188     mTransparencyBitmap = new gchar[byteSize];
189   } else {
190     bool sizeChanged = (size.width != mTransparencyBitmapWidth ||
191                         size.height != mTransparencyBitmapHeight);
192 
193     if (sizeChanged) {
194       ResizeTransparencyBitmap(size.width, size.height);
195     } else {
196       maskChanged = ChangedMaskBits(
197           mTransparencyBitmap, mTransparencyBitmapWidth,
198           mTransparencyBitmapHeight, nsIntRect(0, 0, size.width, size.height),
199           (uint8_t*)mImageSurface->Data());
200     }
201   }
202 
203   if (maskChanged) {
204     UpdateMaskBits(mTransparencyBitmap, mTransparencyBitmapWidth,
205                    mTransparencyBitmapHeight,
206                    nsIntRect(0, 0, size.width, size.height),
207                    (uint8_t*)mImageSurface->Data());
208 
209     // We use X11 calls where possible, because GDK handles expose events
210     // for shaped windows in a way that's incompatible with us (Bug 635903).
211     // It doesn't occur when the shapes are set through X.
212     Display* xDisplay = mWindowSurface->XDisplay();
213     Window xDrawable = mWindowSurface->XDrawable();
214     Pixmap maskPixmap = XCreateBitmapFromData(
215         xDisplay, xDrawable, mTransparencyBitmap, mTransparencyBitmapWidth,
216         mTransparencyBitmapHeight);
217     XShapeCombineMask(xDisplay, xDrawable, ShapeBounding, 0, 0, maskPixmap,
218                       ShapeSet);
219     XFreePixmap(xDisplay, maskPixmap);
220   }
221 }
222 
Commit(const LayoutDeviceIntRegion & aInvalidRegion)223 void WindowSurfaceX11Image::Commit(
224     const LayoutDeviceIntRegion& aInvalidRegion) {
225   RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateDrawTargetForCairoSurface(
226       mWindowSurface->CairoSurface(), mWindowSurface->GetSize());
227   RefPtr<gfx::SourceSurface> surf =
228       gfx::Factory::CreateSourceSurfaceForCairoSurface(
229           mImageSurface->CairoSurface(), mImageSurface->GetSize(),
230           mImageSurface->Format());
231   if (!dt || !surf) {
232     return;
233   }
234 
235   gfx::IntRect bounds = aInvalidRegion.GetBounds().ToUnknownRect();
236   gfx::Rect rect(bounds);
237   if (rect.IsEmpty()) {
238     return;
239   }
240 
241   uint32_t numRects = aInvalidRegion.GetNumRects();
242   if (numRects != 1) {
243     AutoTArray<IntRect, 32> rects;
244     rects.SetCapacity(numRects);
245     for (auto iter = aInvalidRegion.RectIter(); !iter.Done(); iter.Next()) {
246       rects.AppendElement(iter.Get().ToUnknownRect());
247     }
248     dt->PushDeviceSpaceClipRects(rects.Elements(), rects.Length());
249   }
250 
251   if (mIsShaped) {
252     ApplyTransparencyBitmap();
253   }
254 
255   dt->DrawSurface(surf, rect, rect);
256 
257   if (numRects != 1) {
258     dt->PopClip();
259   }
260 }
261 
262 }  // namespace widget
263 }  // namespace mozilla
264