Bug 710533 - Tint the checkerboard with the background color of the page. r=Cwiiis
☠☠ backed out by 420d7b8ed59d ☠ ☠
authorPatrick Walton <pwalton@mozilla.com>
Mon, 23 Jan 2012 20:10:24 -0800
changeset 86394 b77c0c621163f9931e06bd0d84c050074f39d5b9
parent 86393 4035cbbd550b233c28f5ac55e19314f25e72651c
child 86395 e0d47ee06f3467cc937112d2df17969b2ea060e4
push id805
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 18:17:35 +0000
treeherdermozilla-aurora@6fb3bf232436 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersCwiiis
bugs710533
milestone12.0a1
Bug 710533 - Tint the checkerboard with the background color of the page. r=Cwiiis
mobile/android/base/Makefile.in
mobile/android/base/gfx/CheckerboardImage.java
mobile/android/base/gfx/GeckoSoftwareLayerClient.java
mobile/android/base/gfx/LayerController.java
mobile/android/base/gfx/LayerRenderer.java
mobile/android/base/resources/drawable/checkerboard.png
mobile/android/chrome/content/browser.js
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -96,16 +96,17 @@ FENNEC_JAVA_FILES = \
   Tab.java \
   Tabs.java \
   TabsTray.java \
   gfx/BitmapUtils.java \
   gfx/BufferedCairoImage.java \
   gfx/CairoGLInfo.java \
   gfx/CairoImage.java \
   gfx/CairoUtils.java \
+  gfx/CheckerboardImage.java \
   gfx/FloatSize.java \
   gfx/GeckoSoftwareLayerClient.java \
   gfx/InputConnectionHandler.java \
   gfx/IntSize.java \
   gfx/Layer.java \
   gfx/LayerClient.java \
   gfx/LayerController.java \
   gfx/LayerRenderer.java \
@@ -505,17 +506,16 @@ MOZ_ANDROID_DRAWABLES += \
   mobile/android/base/resources/drawable/site_security_level.xml                \
   mobile/android/base/resources/drawable/tabs_button.xml                        \
   mobile/android/base/resources/drawable/tabs_level.xml                         \
   mobile/android/base/resources/drawable/tabs_tray_bg_repeat.xml                \
   mobile/android/base/resources/drawable/tabs_tray_pressed_bg_repeat.xml        \
   mobile/android/base/resources/drawable/tabs_tray_close_button.xml             \
   mobile/android/base/resources/drawable/tabs_tray_list_divider.xml             \
   mobile/android/base/resources/drawable/tabs_tray_list_selector.xml            \
-  mobile/android/base/resources/drawable/checkerboard.png                       \
   mobile/android/base/resources/drawable/shadow.png                             \
   $(NULL)
 
 MOZ_ANDROID_DRAWABLES += $(shell if test -e $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/android-resources.mn; then cat $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/android-resources.mn | tr '\n' ' ';  fi)
 
 RESOURCES=$(RES_LAYOUT) $(RES_LAYOUT_V11) $(RES_VALUES) $(RES_VALUES_V11) $(RES_XML) $(RES_ANIM) $(RES_DRAWABLE_NODPI) $(RES_DRAWABLE_MDPI_V8) $(RES_DRAWABLE_HDPI_V8) $(RES_DRAWABLE_MDPI_V11) $(RES_DRAWABLE_HDPI_V11) $(RES_DRAWABLE_XHDPI_V11) $(RES_DRAWABLE_LAND_MDPI_V14) $(RES_DRAWABLE_LAND_HDPI_V14) $(RES_DRAWABLE_LAND_XHDPI_V14) $(RES_COLOR) $(RES_MENU)
 
 RES_DIRS= \
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/gfx/CheckerboardImage.java
@@ -0,0 +1,150 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick Walton <pcwalton@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.gfx;
+
+import org.mozilla.gecko.GeckoAppShell;
+import android.graphics.Color;
+import java.nio.ByteBuffer;
+import java.nio.ShortBuffer;
+import java.util.Arrays;
+
+/** A Cairo image that displays a tinted checkerboard. */
+public class CheckerboardImage extends CairoImage {
+    // The width and height of the checkerboard tile.
+    private static final int SIZE = 16;
+    // The pixel format of the checkerboard tile.
+    private static final int FORMAT = CairoImage.FORMAT_RGB16_565;
+    // The color to mix in to tint the background color.
+    private static final int TINT_COLOR = Color.GRAY;
+    // The amount to mix in.
+    private static final float TINT_OPACITY = 0.4f;
+
+    private ByteBuffer mBuffer;
+    private int mMainColor;
+
+    /** Creates a new checkerboard image. */
+    public CheckerboardImage() {
+        int bpp = CairoUtils.bitsPerPixelForCairoFormat(FORMAT);
+        mBuffer = GeckoAppShell.allocateDirectBuffer(SIZE * SIZE * bpp / 8);
+        setColor(Color.WHITE);
+    }
+
+    /** Returns the current color of the checkerboard. */
+    public int getColor() {
+        return mMainColor;
+    }
+
+    /** Sets the color of the checkerboard image and regenerates it. */
+    public void setColor(int color) {
+        if (mMainColor == color) {
+            return;
+        }
+
+        mMainColor = color;
+        int tintColor = tint(mMainColor);
+        short mainColor16 = convertTo16Bit(mMainColor), tintColor16 = convertTo16Bit(tintColor);
+
+        short[] mainPattern = new short[SIZE / 2], tintPattern = new short[SIZE / 2];
+        Arrays.fill(mainPattern, mainColor16);
+        Arrays.fill(tintPattern, tintColor16);
+
+        // The checkerboard pattern looks like this:
+        //
+        // +---+---+
+        // | N | T |  N = normal
+        // +---+---+  T = tinted
+        // | T | N |
+        // +---+---+
+
+        mBuffer.rewind();
+        ShortBuffer shortBuffer = mBuffer.asShortBuffer();
+        for (int i = 0; i < SIZE / 2; i++) {
+            shortBuffer.put(mainPattern);
+            shortBuffer.put(tintPattern);
+        }
+        for (int i = SIZE / 2; i < SIZE; i++) {
+            shortBuffer.put(tintPattern);
+            shortBuffer.put(mainPattern);
+        }
+    }
+
+    // Tints the given color appropriately and returns the tinted color.
+    private int tint(int color) {
+        float negTintOpacity = 1.0f - TINT_OPACITY;
+        float r = Color.red(color) * negTintOpacity + Color.red(TINT_COLOR) * TINT_OPACITY;
+        float g = Color.green(color) * negTintOpacity + Color.green(TINT_COLOR) * TINT_OPACITY;
+        float b = Color.blue(color) * negTintOpacity + Color.blue(TINT_COLOR) * TINT_OPACITY;
+        return Color.rgb(Math.round(r), Math.round(g), Math.round(b));
+    }
+
+    // Converts a 32-bit ARGB color to 16-bit R5G6B5, truncating values and discarding the alpha
+    // channel.
+    private short convertTo16Bit(int color) {
+        int r = Color.red(color) >> 3, g = Color.green(color) >> 2, b = Color.blue(color) >> 3;
+        int c = ((r << 11) | (g << 5) | b);
+        // Swap endianness.
+        return (short)((c >> 8) | ((c & 0xff) << 8));
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mBuffer != null) {
+                GeckoAppShell.freeDirectBuffer(mBuffer);
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+
+    @Override
+    public ByteBuffer getBuffer() {
+        return mBuffer;
+    }
+
+    @Override
+    public IntSize getSize() {
+        return new IntSize(SIZE, SIZE);
+    }
+
+    @Override
+    public int getFormat() {
+        return FORMAT;
+    }
+}
+
--- a/mobile/android/base/gfx/GeckoSoftwareLayerClient.java
+++ b/mobile/android/base/gfx/GeckoSoftwareLayerClient.java
@@ -49,25 +49,28 @@ import org.mozilla.gecko.gfx.WidgetTileL
 import org.mozilla.gecko.FloatUtils;
 import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.GeckoEventListener;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import org.json.JSONException;
 import org.json.JSONObject;
 import java.nio.ByteBuffer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Transfers a software-rendered Gecko to an ImageLayer so that it can be rendered by our
  * compositor.
  *
  * TODO: Throttle down Gecko's priority when we pan and zoom.
  */
 public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventListener {
@@ -97,16 +100,18 @@ public class GeckoSoftwareLayerClient ex
 
     // mUpdateViewportOnEndDraw is used to indicate that we received a
     // viewport update notification while drawing. therefore, when the
     // draw finishes, we need to update the entire viewport rather than
     // just the page size. this boolean should always be accessed from
     // inside a transaction, so no synchronization is needed.
     private boolean mUpdateViewportOnEndDraw;
 
+    private static Pattern sColorPattern;
+
     public GeckoSoftwareLayerClient(Context context) {
         mContext = context;
 
         mScreenSize = new IntSize(0, 0);
         mBufferSize = new IntSize(0, 0);
         mFormat = CairoImage.FORMAT_RGB16_565;
 
         mCairoImage = new CairoImage() {
@@ -222,16 +227,19 @@ public class GeckoSoftwareLayerClient ex
             mGeckoViewport = new ViewportMetrics(viewportObject);
             mGeckoViewport.setSize(viewportSize);
 
             LayerController controller = getLayerController();
             PointF displayportOrigin = mGeckoViewport.getDisplayportOrigin();
             mTileLayer.setOrigin(PointUtils.round(displayportOrigin));
             mTileLayer.setResolution(mGeckoViewport.getZoomFactor());
 
+            int backgroundColor = parseColorFromGecko(viewportObject.getString("backgroundColor"));
+            controller.setCheckerboardColor(backgroundColor);
+
             if (onlyUpdatePageSize) {
                 // Don't adjust page size when zooming unless zoom levels are
                 // approximately equal.
                 if (FloatUtils.fuzzyEquals(controller.getZoomFactor(),
                         mGeckoViewport.getZoomFactor()))
                     controller.setPageSize(mGeckoViewport.getPageSize());
             } else {
                 Log.d(LOGTAG, "Received viewport update from gecko");
@@ -449,10 +457,28 @@ public class GeckoSoftwareLayerClient ex
 
             // Redraw everything.
             Rect rect = new Rect(0, 0, mBufferSize.width, mBufferSize.height);
             GeckoAppShell.sendEventToGecko(new GeckoEvent(GeckoEvent.DRAW, rect));
         } else if ("Viewport:UpdateLater".equals(event)) {
             mUpdateViewportOnEndDraw = true;
         }
     }
+
+    // Parses a color from an RGB triple of the form "rgb([0-9]+, [0-9]+, [0-9]+)". If the color
+    // cannot be parsed, returns white.
+    private static int parseColorFromGecko(String string) {
+        if (sColorPattern == null) {
+            sColorPattern = Pattern.compile("rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)");
+        }
+
+        Matcher matcher = sColorPattern.matcher(string);
+        if (!matcher.matches()) {
+            return Color.WHITE;
+        }
+
+        int r = Integer.parseInt(matcher.group(1));
+        int g = Integer.parseInt(matcher.group(2));
+        int b = Integer.parseInt(matcher.group(3));
+        return Color.rgb(r, g, b);
+    }
 }
 
--- a/mobile/android/base/gfx/LayerController.java
+++ b/mobile/android/base/gfx/LayerController.java
@@ -76,18 +76,21 @@ public class LayerController {
     private ViewportMetrics mViewportMetrics;   /* The current viewport metrics. */
 
     private PanZoomController mPanZoomController;
     /*
      * The panning and zooming controller, which interprets pan and zoom gestures for us and
      * updates our visible rect appropriately.
      */
 
-    private OnTouchListener mOnTouchListener;   /* The touch listener. */
-    private LayerClient mLayerClient;           /* The layer client. */
+    private OnTouchListener mOnTouchListener;       /* The touch listener. */
+    private LayerClient mLayerClient;               /* The layer client. */
+
+    /* The new color for the checkerboard. */
+    private int mCheckerboardColor;
 
     private boolean mForceRedraw;
 
     /* The extra area on the sides of the page that we want to buffer to help with
      * smooth, asynchronous scrolling. Depending on a device's support for NPOT
      * textures, this may be rounded up to the nearest power of two.
      */
     public static final IntSize MIN_BUFFER = new IntSize(512, 1024);
@@ -139,17 +142,16 @@ public class LayerController {
         return mViewportMetrics.getOrigin();
     }
 
     public float getZoomFactor() {
         return mViewportMetrics.getZoomFactor();
     }
 
     public Bitmap getBackgroundPattern()    { return getDrawable("background"); }
-    public Bitmap getCheckerboardPattern()  { return getDrawable("checkerboard"); }
     public Bitmap getShadowPattern()        { return getDrawable("shadow"); }
 
     public GestureDetector.OnGestureListener getGestureListener()                   { return mPanZoomController; }
     public SimpleScaleGestureDetector.SimpleScaleGestureListener getScaleGestureListener() {
         return mPanZoomController;
     }
     public GestureDetector.OnDoubleTapListener getDoubleTapListener()               { return mPanZoomController; }
 
@@ -346,10 +348,21 @@ public class LayerController {
      */
     public boolean onTouchEvent(MotionEvent event) {
         if (mPanZoomController.onTouchEvent(event))
             return true;
         if (mOnTouchListener != null)
             return mOnTouchListener.onTouch(mView, event);
         return false;
     }
+
+    /** Retrieves the color that the checkerboard should be. */
+    public int getCheckerboardColor() {
+        return mCheckerboardColor;
+    }
+
+    /** Sets a new color for the checkerboard. */
+    public void setCheckerboardColor(int newColor) {
+        mCheckerboardColor = newColor;
+        mView.requestRender();
+    }
 }
 
--- a/mobile/android/base/gfx/LayerRenderer.java
+++ b/mobile/android/base/gfx/LayerRenderer.java
@@ -75,16 +75,17 @@ public class LayerRenderer implements GL
      */
     private static final int MAX_FRAME_TIME = 16;   /* 1000 ms / 60 FPS */
 
     private static final int FRAME_RATE_METER_WIDTH = 64;
     private static final int FRAME_RATE_METER_HEIGHT = 32;
 
     private final LayerView mView;
     private final SingleTileLayer mBackgroundLayer;
+    private final CheckerboardImage mCheckerboardImage;
     private final SingleTileLayer mCheckerboardLayer;
     private final NinePatchTileLayer mShadowLayer;
     private final TextLayer mFrameRateLayer;
     private final ScrollbarLayer mHorizScrollLayer;
     private final ScrollbarLayer mVertScrollLayer;
     private final FadeRunnable mFadeRunnable;
     private RenderContext mLastPageContext;
     private int mMaxTextureSize;
@@ -97,18 +98,18 @@ public class LayerRenderer implements GL
     public LayerRenderer(LayerView view) {
         mView = view;
 
         LayerController controller = view.getController();
 
         CairoImage backgroundImage = new BufferedCairoImage(controller.getBackgroundPattern());
         mBackgroundLayer = new SingleTileLayer(true, backgroundImage);
 
-        CairoImage checkerboardImage = new BufferedCairoImage(controller.getCheckerboardPattern());
-        mCheckerboardLayer = new SingleTileLayer(true, checkerboardImage);
+        mCheckerboardImage = new CheckerboardImage();
+        mCheckerboardLayer = new SingleTileLayer(true, mCheckerboardImage);
 
         CairoImage shadowImage = new BufferedCairoImage(controller.getShadowPattern());
         mShadowLayer = new NinePatchTileLayer(shadowImage);
 
         IntSize frameRateLayerSize = new IntSize(FRAME_RATE_METER_WIDTH, FRAME_RATE_METER_HEIGHT);
         mFrameRateLayer = TextLayer.create(frameRateLayerSize, "-- ms/--");
 
         mHorizScrollLayer = ScrollbarLayer.create(false);
@@ -166,17 +167,17 @@ public class LayerRenderer implements GL
                 }
             }
             mLastPageContext = pageContext;
 
             /* Update layers. */
             if (rootLayer != null) updated &= rootLayer.update(gl, pageContext);
             updated &= mBackgroundLayer.update(gl, screenContext);
             updated &= mShadowLayer.update(gl, pageContext);
-            updated &= mCheckerboardLayer.update(gl, screenContext);
+            updateCheckerboardLayer(gl, screenContext);
             updated &= mFrameRateLayer.update(gl, screenContext);
             updated &= mVertScrollLayer.update(gl, pageContext);
             updated &= mHorizScrollLayer.update(gl, pageContext);
 
             /* Draw the background. */
             mBackgroundLayer.draw(screenContext);
 
             /* Draw the drop shadow, if we need to. */
@@ -329,16 +330,33 @@ public class LayerRenderer implements GL
             public void run() {
                 Context context = mView.getContext();
                 SharedPreferences preferences = context.getSharedPreferences("GeckoApp", 0);
                 mShowFrameRate = preferences.getBoolean("showFrameRate", false);
             }
         }).start();
     }
 
+    private void updateCheckerboardLayer(GL10 gl, RenderContext renderContext) {
+        int newCheckerboardColor = mView.getController().getCheckerboardColor();
+        if (newCheckerboardColor == mCheckerboardImage.getColor()) {
+            return;
+        }
+
+        mCheckerboardLayer.beginTransaction();
+        try {
+            mCheckerboardImage.setColor(newCheckerboardColor);
+            mCheckerboardLayer.invalidate();
+        } finally {
+            mCheckerboardLayer.endTransaction();
+        }
+
+        mCheckerboardLayer.update(gl, renderContext);
+    }
+
     class FadeRunnable implements Runnable {
         private boolean mStarted;
         private long mRunAt;
 
         void scheduleStartFade(long delay) {
             mRunAt = SystemClock.elapsedRealtime() + delay;
             if (!mStarted) {
                 mView.postDelayed(this, delay);
deleted file mode 100644
index 57cfbe80fdcfaa6866ade23dd363dbb3f0f8c0c2..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -797,17 +797,28 @@ var BrowserApp = {
       if (transformChanged)
         tab.updateTransform();
       // finally, let java know where we ended up
       tab.sendViewportUpdate();
     }
   },
 
   getDrawMetadata: function getDrawMetadata() {
-    return JSON.stringify(this.selectedTab.viewport);
+    let viewport = this.selectedTab.viewport;
+
+    // Sample the background color of the page and pass it along. (This is used to draw the
+    // checkerboard.)
+    let browser = this.selectedBrowser;
+    if (browser) {
+      let { contentDocument, contentWindow } = browser;
+      let computedStyle = contentWindow.getComputedStyle(contentDocument.body);
+      viewport.backgroundColor = computedStyle.backgroundColor;
+    }
+
+    return JSON.stringify(viewport);
   },
 
   observe: function(aSubject, aTopic, aData) {
     let browser = this.selectedBrowser;
     if (!browser)
       return;
 
     if (aTopic == "Session:Back") {